# Acurast Documentation
> Complete reference documentation for Acurast - the Real Decentralized Compute Network powered by smartphones.
This file contains all documentation content in a single document following the llmstxt.org standard.
## Application Layer
In the Internet of today, almost every application relies heavily on auxiliary systems. Whether external APIs are used for authentication, basic infrastructure (hosting), data availability, reliance, which can benefit from extending or replacing services and core elements with confidential applications, essentially eliminating a host of threat events. The possibilities with Acurast are near-endless since today's centralized Internet is heavily centralized both logically and in terms of trust anchors.
## Use Case Examples
Acurast's architecture transforms the way applications are designed and deployed. Acurast achieves an unparalleled Developer Experience (DevEx) by offering the [Acurast Hub](https://hub.acurast.com) to developers, where a self-service model allows developers to integrate and develop their applications. The following subsections outline potential algorithms and use-cases that can be deployed in a confidential manner through Acurast.
### Zero-Knowledge Proof Applications
Zero-Knowledge Proof protocols find vast application in blockchains [[1]](#references). Potential use cases range from anonymous voting systems [[2]](#references), to secure and privacy-preserving digital assets exchange, secure remote biometric authentication, or Proof-of-Reserves [[3]](#references).
Acurast can be leveraged in multiple areas of ZKP applications, for instance, to offload high intensity computation in a confidential manner [[4]](#references), or to form sub-consortia of processors that can, for instance, mix and generate proofs that the mixing has been performed correctly.
### Privacy-Preserving Mixing
With ZKPs, privacy mixing can occur in a way that allows transactions to be validated without exposing the details of those transactions. However, the mixing is not limited to Web3 transactions and can be extended to other data sensitive to privacy (_e.g.,_ metadata of internet traffic or metadata of files).
### Secure Multi-Party Computation
Secure Multi-Party Computation (SMPC) is a cryptographic primitive that enables distributed parties to conduct joint computations without revealing their own private inputs and outputs to the computation [[7]](#references). For instance, doctors may query a database containing private information, or banks may invest in a fund that must satisfy both banks private constraints. Usually, one trusted entity must know the inputs from all the participants, however, if no Trusted Third Party (TTP) is available or suitable, privacy concerns are evident [[7]](#references). With Acurast, a processors can be selected for SMPC algorithms to execute _e.g.,_ a permissionless poker game [[8]](#references).
### Blockchain Infrastructure
Blockchain networks' rising adoption and complexity of blockchain networks has led to an increasing need for a reliable blockchain infrastructure. Novel incentive structures (_e.g.,_ slashing in PoS) have intensified this further. It is crucial that this infrastructure is neither logically nor physically centralized because it would introduce new trust assumptions that undermine the permissionless nature of blockchains. For these services, Acurast can serve as a decentralized, serverless backend.
### Incorruptible Sequencer
A huge issue in public blockchains is Blockchain Extractable Value (BEV), and Miner Extractable Value (MEV), where DeFi users are at risk of being attacked [[9]](#references) (_e.g.,_ frontrunning and sandwich attacks [[10]](#references). With Acurast, processors can serve as confidential and confidential sequencers, ensuring that the order of transactions is deterministic and immune to external influence.
### Beyond Oracles: Serverless Applications
Oracles and on-chain automation are key ingredients of blockchain infrastructure. While oracles enable external data to be imported into the blockchain, oracles mainly deal with data retrieval and validation, ensuring that accurate and reliable data is fed into smart contracts. However, on-chain automation has a broader scope, encompassing automated liquidity provision, periodic settlements, debt restructuring, yield harvesting, and much more. The emphasis here is on action and execution based on specific conditions.
On-chain automation is about executing predefined actions without manual intervention, based on conditions or triggers that may come from oracles or on-chain data and events.
### Native Cross-Chain DeFi
Native cross-chain DeFi capabilities have been developed to allow seamless interactions and transactions between blockchains, creating a more inclusive and expansive financial ecosystem. Applying Account Abstraction allows for the design of accounts that can interact and integrate across various platforms and protocols, simplifying user experiences and opening the door for innovative use cases.
### Data Availability as-a-Service
(DAaaS) provides decentralized storage solutions to ensure data remains accessible and intact, fortifying the robustness of the entire decentralized ecosystem.
### Decentralized Scraping Infrastructure
Of all the Internet traffic in 2022, 47.4 % was automated traffic, also commonly referred to bots [[5]](#references). Of that automated traffic, 30.2 % were bad bots, while good bots are on the rise too, accounting for 17.3 %. The percentage of human traffic continues its downward trend, from 57.7 % in 2021 to 52.6 % in 2022. Bots, in that context do not refer to volumetric Distributed Denial-of-Service attacks, but the bot activity on layer 7 of the OSI model. In general, good bots are important for various business models and applications, since they are scraping data and feed models for decision making or business logic directly.
With Acurast, the scraping infrastructure can be fully decentralized logically and physically, leveraging the network of processor resources that confidentially execute these tasks, without leaking any data about the querying party. For example, when intelligence is gathered (_e.g.,_ for investment or merger decisions), a large amount of data must be scraped confidentially.
### Artifical Intelligence
The recent surge of Artificial Intelligence (AI) applications has led to increased research and development in these areas. While the potential of these technologies are vast, the risks associated to the centralized deployment and privacy of data is crucial to assess carefully. In Acurast, the _Singularity_ module allows the execution of AI in a decentralized and confidential fashion. _E.g.,_ Acurast enables Large Language Models (LLM) to be executed in a federated, privacy-preserving, and trustless way [[11]](#references).
### Internet of Things
In general terms, the Internet of Things (IoT) refers to interconnected computing devices that form a network and monitor environmental variables (_e.g.,_ health care [[12]](#references). Often, due to its limited resources, heterogeneity, and lack of computing power, IoT faces many security and privacy challenges. Data is transferred between IoT devices without human intervention, making confidentiality an essential aspect in network and trust management. The Acurast _Mesh_ module creates space for novel IoT use cases. Depending on the processor used, built-in Bluetooth modules or WiFi direct connections can be used to collect metrics or data and confidentially process the data.
#### References
[1] X. Sun, F. R. Yu, P. Zhang, Z. Sun, W. Xie, and X. Peng, _A Survey on Zero-Knowledge Proof in Blockchain,_ IEEE Network, Vol. 35, No. 4, pp. 198–205, 2021.
[2] C. Killer, M. Eck, B. Rodrigues, J. von der Assen, R. Staubli, and B. Stiller, _ProvotuMN: Decentralized, Mix-Net-based, and Receipt-free Voting System,_ in 2022 IEEE International Conference on Blockchain and Cryptocurrency (ICBC), 2022, pp. 1–9.
[3] C. Killer, B. Rodrigues, E. J. Scheid, M. F. Franco, and B. Stiller, _Blockchain-based Voting Considered Harmful?_ IEEE Transactions on Network and Service Management, pp. 1–1, 2022.
[4] N. Ni and Y. Zhu, _Enabling Zero Knowledge Proof by Accelerating zk-SNARK Kernels on GPU,_ Journal of Parallel and Distributed Computing, vol. 173, pp. 20–31, 2023.
[5] Imperva, “Imperva 2023 Bad Bot Report,” https://www.imperva.com/resources/resource-library/reports/2023-imperva-bad-bot-report/, May, 2023.
[6] D. C. Nguyen, M. Ding, P. N. Pathirana, A. Seneviratne, J. Li, and H. V. Poor, _Federated Learning for Internet of Things: A Comprehensive Survey,_ IEEE Communications Surveys and Tutorials, Vol. 23, No. 3, pp. 1622–1658, 2021.
[7] W. Du and M. J. Atallah, _Secure Multi-Party Computation Problems and Their Applications: A Review and Open Problems,_ in Proceedings of the 2001 Workshop on New Security Paradigms, ser. NSPW ’01. New York, NY, USA: Association for Computing Machinery, 2001, p. 13–22. Available on: https://doi.org/10.1145/508171.508174
[8] R. Kumaresan, T. Moran, and I. Bentov, “How to use bitcoin to play decentralized poker,” in Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security, Ser. CCS ’15. New York, NY, USA: Association for Computing Machinery, 2015, p. 195–206. [Online]. Available on: https://doi.org/10.1145/2810103.2813712
[9] L. Zhou, X. Xiong, J. Ernstberger, S. Chaliasos, Z. Wang, Y. Wang, K. Qin, R. Wattenhofer, D. Song, and A. Gervais, _SoK: Decentralized Finance (DeFi) Attacks,_ 2023
[10] P. Daian, S. Goldfeder, T. Kell, Y. Li, X. Zhao, I. Bentov, L. Breidenbach, and A. Juels, _Flash Boys 2.0: Frontrunning, Transaction Reordering, and Consensus Instability in Decentralized Exchanges_, 2019.
[11] D. C. Nguyen, M. Ding, P. N. Pathirana, A. Seneviratne, J. Li, and H. V. Poor, _Federated Learning for Internet of Things: A Comprehensive Survey,_ IEEE Communications Surveys and Tutorials, vol. 23, no. 3, pp. 1622–1658, 2021.
[12] S. B. Baker, W. Xiang, and I. Atkinson, _Internet of Things for Smart Healthcare: Technologies, Challenges, and Opportunities,_ IEEE Access, Vol. 5, pp. 26 521–26 544, 2017.
---
## Overview
Acurast separates the consensus, execution, and application layer (c.f., Fig. 1).
Acurast's cloud architecture transforms the way applications are designed and deployed. The modular nature allows native settlements and universal interoperability of ecosystems, i.e., Web3 → Web3 and Web3 → Web2. Ultimately, Acurast serves as a decentralized application platform that ensures the privacy and verifiability of data, without introducing new trusted entities.
Figure 1: Acurast Architecture{" "}
### Consensus Layer
The consensus layer is the permissionless foundation of Acurast, where the Matcher pairs developer's deployments with processors, as outlined in the End-to-End flow (_c.f.,_ [End-to-End Deployment Execution](/acurast-protocol/architecture/end-to-end) ). The second core part of the consensus layer is the reputation engine (_c.f.,_ ), which as sures that the reputation scores of processors are correctly updated and incentivize honest behavior.
### Execution Layer
The Execution Layer has two significant components. The first one is composed of the different processor runtimes, namely the Acurast Secure Hardware Runtime (_c.f.,_ [ASHR](/acurast-protocol/architecture/execution-layer#acurast-secure-hardware-runtime-ashr)) and the Acurast Zero-Knowledge Runtime (_c.f.,_ [AZKR](/acurast-protocol/architecture/execution-layer#acurast-zero-knowledge-runtime)). The second key component is the Acurast Universal Interoperability Layer, which contains multiple Modules that enable native interaction with different ecosystems.
### Application Layer
The third layer is the application layer, where Web2 or Web3 applications run (\cf Sec.~\ref{sec:application_layer}). Although a host of DeFi protocols already make use of Acurast, Acurast will infuse the development of a wide range of use cases that were previously not possible to implement in a confidential and decentralized manner.
## Matcher
The Acurast Matcher is a centerpiece of the consensus layer, combining the matching (i.e., the scheduling of deployments and enabling the liquid matching) of the Processor's computational resources and Developers. The Matcher plays an essential role in the definition, agreement, and enforcement of value exchange between processors and developers.
The Matcher is where the liquid matching engine pairs the advertised processor resources with the defined requirements of the developers. The Matcher natively supports various price-finding mechanisms (e.g., auctions and advertisements), making Developer Experience (DevEx) highly accessible and seamless.
Every agreement between processor and developer is specified in an entity called deployment. The deployment specifies (i) a set of instructions that are executed on the processor, (ii) its scheduling parameters, and (iii) the destination configuration (i.e., where the output is further processed or persisted).
### Compute Costs & Rewards
When scheduling a deployment, the developer defines the compute cost for the execution. The cost can be defined in native cACU/ACU tokens. This mechanism allows for deterministic financial planning of executions for developers.
:::info
**All compute costs are paid as gas (transaction) fees. There is no direct reward flow from developers to processors.**
:::
Instead, processors earn rewards through the **[staking pools](/token-holders/staking/overview)**. When processors execute deployments, they receive a [Deployment Execution Bonus](/processors/rewards#deployment-execution-bonus): a bonus weight on their Benchmark Metrics that increases their scoring—and therefore their share of rewards—in both the Staked Compute Pool and the Compute Pool (Base Benchmarking Rewards) for that epoch. This mechanism ensures processors are incentivized to execute deployments and burn gas fees.
## Implementation
Acurast leverages a Substrate Runtime consisting of multiple Substrate Pallets for the Acurast Protocol (_c.f.,_ [GitHub](https://github.com/acurast) ).
---
## Consensus Layer
The Permissionless Consensus layer forms the base of the Acurast protocol and is based on a variant of the Nominated Proof-of-Stake (NPoS) algorithm [[1]](#references). Unlike traditional Proof-of-Stake (PoS) networks, there are *validators* and *nominators* in NPoS. Block validators verify transactions to be included in the next block, similar to traditional PoS block validators. The key difference is that instead of being randomly chosen, the *validator* nodes are *nominated* by another node.
In Acurast's NPoS, an unlimited amount of token holders can participate as *nominators*, backing a limited set of *validators* with their stake. Having a limited set of *validators* assures the long-term scalability of the consensus, allowing the increase of the maximum threshold through governance decisions. An unlimited set of *nominators* assure that higher value is at stake, assuring a high level of security. Due to its upgradable runtimes, consensus parameters are configurable by governance decisions, *e.g.,* The maximum number of *validators*, and the minimum amount of stake for *validators*.
The Acurast NPoS system heavily leverages nominators to ensure network integrity. Nominators and validators have multiple aligned incentives. Nominators hold a financial stake in the system, which means that they could suffer a loss if a validator acts maliciously. In addition, nominators are financially rewarded for selecting a reliable and high-performance validator. Both nominators and validators have reputational stakes, with the credibility of nominators affected by their validator choices. Finally, the limited number of validator slots in NPoS structures creates a competitive environment, pushing nominators to select the most efficient validators, but also democratizes the process, making voting power essential.
NPoS has proven to be an efficient way to achieve high levels of security, scalability, and decentralization over time. Congruent to [[1]](#references), nominators share the rewards, or eventual slashings, with the validators they nominated on a *per-staked-ACU* basis.
#### References
[1] J. Burdges, A. Cevallos, P. Czaban, R. Habermeier, S. Hosseini, F. Lama, H. K. Alper, X. Luo, F. Shirazi, A. Stewart, and G. Wood, *Overview of Polkadot and its Design Considerations,* 2020.
---
## End-to-End Deployment Execution
Acurast introduces a paradigm shift in verifiable and confidential computation, advancing the way decentralized applications are developed and deployed. To emphasize the inner workings of Acurast, the following description follows a `deployment` from definition and deployment to completion (_c.f.,_ Fig. 1).
Figure 1: End-to-End Deployment Execution{" "}
### (1) `Deployment` Registration
As a first step, developers define their `deployment` details. For example, at what destination the `deployment` should be _settled_, i.e., on which protocol the `deployment` output should be persisted (e.g., on Bitcoin Mainnet). After that, the developer can select `ready-to-deploy` templates, which can be adapted and changed to the developer's needs, or a custom `deployment` can be defined.
Depending integration level of the destination ecosystem with Acurast, the pre-payments for gas fees and rewards are settled in the native currency the developer prefers (e.g., native TEZ for Tezos or ETH for Ethereum) or in native Acurast ACU tokens.
Next, the developer must state on which processors the `deployment` should be executed, either (a) on personal processors, or (b) on selected, known processors (e.g., known trusted entities), or (c) on public processors. For (a), a processor reward is not required, since it is a permissioned setting. For (b) a reward is optional, and for (c) the liquid matching engine and the Acurast Matcher will pair processor resources with developers' `deployment`s.
In addition, more details of the `deployment` need to be declared, such as _scheduling_ parameters, including start time, end time, the interval between executions, as well as the duration in milliseconds and the maximum start delay in milliseconds. Furthermore, specific resource management parameters, such as memory usage, network requests, and storage requirements of the `deployment` need to be declared. Finally, the reward for the execution of the `deployment` should be declared, as well as the minimum reputation (only applies to (c) public processors). Then the `deployment` will be persisted on the Acurast Consensus Layer and reaches `OPEN` state (c.f., Fig. 2).
Figure 2: States of a deployment{" "}
### (2) `Deployment` Acknowledgment
Second, the processor acknowledges the `deployment` and fetches the details from the Acurast chain. Depending on the fulfillment definition of the respective `deployment`, the Merkle root of the `deployment` with proof of assignment is persisted on the target destination (e.g., on a different target chain). Now the `deployment` reaches the `MATCHED` state, and no other processors will attempt to acknowledge it.
A prerequisite for assigning the `deployment` to the processor is that the processor can execute the `deployment` in full, following the _all-or-nothing_ principle. Since `deployment`s can have different scheduling configurations (e.g., on demand, every minute, etc.). Therefore, if the processor acknowledges that all slots can be adhered to, the `deployment` reaches the `ASSIGNED` state.
### (3) `Deployment` Execution
Next, the `deployment_script` is executed in the processor runtime. In the illustrated example of Fig. 1, the execution is performed inside of the Acurast Secure Hardware Runtime (ASHR), _i.e.,_ because _confidentiality_ is ascertained by secure hardware _e.g.,_ an isolated and external coprocessor (Google's Titan M2 Chip). Other runtimes (e.g., the Acurast Zero-Knowledge Runtime (AZKR)) may provide additional soundness guarantees.
### (4) `Deployment` Fulfillment
\textbf{(4) `deployment` Fulfillment:} Once the `deployment` execution is completed, the output is delivered to the declared destination, which could be another Web3 system (e.g., Tezos, Ethereum) or a Web2 system (e.g., REST-API, FL model) that receives the output. In case of a cross-chain transaction, the processor settles the gas fees on the destination chain, since the developer has locked the necessary reward and gas fee amount up front when registering the `deployment`.
### (5) `Deployment` Reporting
After completion, the processor reports back to the Acurast Consensus Layer, more specifically to the reputation engine. If fulfillment was successful, the report contains a transaction hash of the target chain containing the fulfillment transaction. In case of failure, the report contains error messages. Finally, the `deployment` is now in `DONE` state.
To assure the reliability of the Acurast protocol, the reputation engine is continuously fed with reliability metrics, for instance right after `deployment` completion or failure.
---
## Execution Layer
Acurast's execution layer is modular, allowing the flexible selection of runtimes according to the requirements of the use-case and the `deployment`, respectively. Decoupling the execution layer from the consensus and application layer allows the long-term evolution of runtimes, avoiding dependency lock-ins. Additionally, it ensures the highest level of service and confidentiality because security models can iteratively evolve with upgrades as novel threats emerge or new requirements arise.
Acurast offers native and straightforward bootstrapping of permissioned consortia. Depending on the requirements, either _(a)_ developers can directly leverage the Acurast Matcher to select from a public pool of processors, or _(b)_ or use dedicated processors (_e.g.,_ from trusted entities, or use developer-supported self-service processors). Such composability allows developers to customize access control and define individual trust models depending on the `deployments` that are executed.
The Acurast execution layer natively offers two runtimes, the _(1)_ Acurast Secure Hardware Runtime (ASHR) and _(2)_ Acurast Zero-Knowledge Runtime (AZKR).
### Acurast Secure Hardware Runtime (ASHR)
The Acurast Secure Hardware Runtime (ASHR) is a generic approach to achieve a confidential execution layer while assuming a timely threat model, thus ensuring the highest possible level of security. The security guarantees achieved by secure hardware are generally highly divergent, from virtual processors to on-SoC processors, and finally, to the current bleeding edge of an _external coprocessor_, which is a physically separated and independent chip, dedicated to only security-sensitive operations [[1]](#references). The current ASHR implementation is based on coprocessors provided by the Google Titan chip [[2]](#references). The Titan chip has not been compromised, unlike most secure hardware platforms. Although high-reward bug bounties[[3]](#references) and the highest zero-day vulnerability payouts[[4]](#references) do not _guarantee_ security, they are a solid indication of the security level achieved.
### Rationale on using Mobile Hardware
Smartphones are among the most complex cases when it comes to information security. Their computing power has grown to the point of being almost indistinguishable from computers, they store the most valuable personal data and are used to carry out security-sensitive activities, which make them extremely attractive targets for attackers. With such a wide-ranging threat model and the fact that the vast computing base of a modern OS cannot be fully trusted, vendors have begun to use hardware to improve the security of their systems [[1]](#references).
### On TEEs and Hardware Security
Usually, Trusted Execution Environments (TEE) are created by integrating protection mechanisms directly into the processor or using dedicated external secure elements. However, both approaches only cover a narrow threat model, resulting in very limited security guarantees. For instance, enclaves nested in the application processor provide weak isolation and weak protection against side-channel attacks. Regardless of the approach used, TEEs often lack the ability to establish secure communication with peripherals, and most operating systems run inside TEEs do not provide state-of-the-art defense strategies, making them vulnerable to various attacks. Arguably, TEEs, such as Intel SGX [[5,6]](#references) or ARM TrustZone [[7]](#references), implemented on the main application processor, are insecure, _particularly_ when considering side-channel attacks. For that reason, ASHR is based on the bleeding edge of a dedicated coprocessor.
## Acurast Zero-Knowledge Runtime
The Acurast ZKP-based Runtime (AZKR) is another approach towards achieving general-purpose verifiable computation by leveraging recursive ZKP, which can generate and aggregate proofs for any computation. While the ASHR provides a performance advantage over the AZKR, the trust model of ZK-based protocols draws its core trust assumptions from the cryptographic scheme, not hardware-based security assumptions. The ASHR can scale horizontally across different applications; the AZKR requires specific circuits, assumptions, and requirements. On the other hand, ASHR provides an isolated environment for sensitive code, optimized for efficiency. Finally, trust-wise, ASHR rely on key attestation procedures and hardware-based trust assumptions, while AZKR systems mainly rely on semi-trusted sequencers and the reliance on cryptographic soundness.
#### References
[1] P. T. Maxime Rossi Bellom, Damiano Melotti, _2021: A Titan
M Odyssey_, 2021. Available on: https://i.blackhat.com/EU-21/Wednesday/EU-21-Rossi-Bellom-2021_A_Titan_M_Odyssey-wp.pdf
[2] C. Wankhede, _What is the Titan M2 security chip in Google’s Pixel phones?_ https://www.androidauthority.com/titan-m2-google-3261547/, Jan 2023.
[3] J. Reed, _Google’s bug bounty hits $12 million: What about the risks?_ https://securityintelligence.com/news/googles-bug-bounty-hits-12-million-what-about-the-risks-2/, May 2023.
[4] ZERODIUM, _ZERODIUM Payouts for Mobiles_, https://zerodium.com/program.html
[5] S. van Schaik, A. Kwong, and D. Genkin, _SGAxe: How SGX Fails in Practice_, 2020. Available on: https://api.semanticscholar.org/CorpusID:220248073
[6] S. van Schaik, A. Seto, T. Yurek, A. Batori, B. AlBassam, C. Garman, D. Genkin, A. Miller, E. Ronen, and Y. Yarom, _SoK: SGX. Fail: How stuff get eXposed_, 2022.
[7] S. Pinto and N. Santos, “Demystifying Arm TrustZone: A Comprehensive Survey,” ACM Computing Survey, Vol. 51, No. 6, Jan 2019. Available on
https://doi.org/10.1145/3291047
---
## Instances
Acurast will bootstrap three distinct networks: (a) the Acurast Testnet, (b) the Acurast Canary, and (c) the Acurast Mainnet.
### Acurast Devnet
The test network uses tokens with no governance and no staking utility. The service level provided is for test applications and experimental Proof-of-Concepts. The Acurast Testnet provides instant update cycles, whenever the code is ready, following the philosophy “Build, Break, and Fix Faster”. Testnet updates do not require governance, the Testnet is linked to the [Rococo](https://substrate.io/developers/rococo-network/) relay network.
- [Block Explorer ↗](https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Facurast-devnet-ws.prod.gke.papers.tech#/explorer)
- [Acurast Hub ↗](https://hub.acurast.com)
### Acurast Canary
The canary network uses tokens with governance and staking utility. The service level provided is for production-grade applications. Acurast Canary provides shorter update cycles than the Mainnet, and also faster releases and deprecation of features. The canary network follows quarterly update cycles with a 9-month support span. Forkless network upgrades are activated and validated through the Canary network’s governance process, and it is linked to the [Kusama](https://kusama.network/) relay network.
1. **Pre-Net**
Launched on programmer’s day September 13th, 2023. The Pre-Net launched without the governance process and has the Acurast Association’s collators producing blocks. Since there is no block reward, there will be no inflation in this phase. The purpose of this phase is to allow Developers and Processors to onboard and start exchanging their resources, all while providing a production-grade service level. During the pre-net phase, the governance and token transfers will remain disabled and no active transferring of tokens is expected at this stage.
Therefore, developers who want to build zero-trust applications can request tokens through the social engagement faucet, more on this shortly.
1. **Alpha-Net**
The purpose of this phase is to onboard external Collators and enable nominated Proof of Stake for the selection of those Collators.
Comparable to Curve’s voted escrow model, the stake weight is based on the user’s selected lock time duration of the stake provided. The exact mechanics will be outlined in a future blog post.
With the activation of the Alpha-Net, the block production will be rewarded, the rewards will be distributed from the Acurast protocol's adaptive inflation model. Sudo will remain active for this phase.
1. **Beta-Net**
This phase removes sudo and migrates governance to the active stakers and enables token transfers.
Acurast Canary will pave the road for the Acurast Mainnet launch going forward.
### Acurast Mainnet
The Acurast Mainnet uses tokens with governance and staking utility. The service level provided is for production-grade applications. The Mainnet follows yearly update cycles including features only after thorough battle testing on the Acurast Canary with a long-term support span of 5 years. The Mainnet also follows forkless upgrades activated through the Mainnet governance process and it is linked to the [Polkadot](https://www.polkadot.network/) relay network.
Launched in Q1 2026, connected to Polkadot.
---
## Audits
# Security Audits
Security is a top priority for Acurast. Regular security audits are conducted security to ensure the safety and reliability the Acurast protocol, its components and infrastructure.
As the protocol and ecosystem continue to evolve, the Acurast Association is committed to conduct regular security audits across all components of the Acurast infrastructure.
The table below will be updated over time as new audits are completed, ensuring transparency and maintaining the highest security standards for users and developers.
The following table lists all finalized security audits conducted for Acurast components:
| Date | Name | Auditor | Component | Link |
| ---------- | ---------------------------------- | ----------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------ |
| 2026-03-11 | Acurast Compute Pallet Audit | [Monethic](https://monethic.io/) | Acurast Compute Pallet Audit | [View Report](/audits/20261103_Monoethic_Compute_Pallet.pdf)|
| 2025-10-23 | Acurast Chain Audit | [Monethic](https://monethic.io/) | Acurast Substrate and Pallets Audit | [View Report](/audits/20251023_Monoethic_Substrate.pdf) |
| 2025-10-23 | Acurast Token Smart Contract Audit | [inference](https://inference.ag/) | ACU ERC20 Token Contract for EVM Deployments | [View Report](/audits/20250210_InferenceAG_AcurastERC20Token.pdf) |
| 2024-03-27 | Pentest tzBTC Android App | [Compass Security](https://www.compass-security.com/) | Acurast Processor App (Android) | [View Report](/audits/20240327_Compass_Security_Processor_App_xcBTC.pdf) |
---
## Acurast Collator Onboarding
This page describes the steps to take to onboard a collator on the Acurast Canary chain.
## Pre registration checks
- Make sure the node is setup with an identity as described in the [Node Setup ↗](/acurast-protocol/node-setup) guide.
- Make sure the node is fully synced.
- Make sure the node was started with the `--collator` flag.
- Make sure the node is running on hardware that meet the minimum requirements. It is possible to check by looking at the node logs when it first starts:
If the hardware is good enough, there should not be any warning log message after the benchmarks logs shown in the screenshot above.
## Set session key
A session key needs to be registered. To do that, first perform the `author_rotateKeys` RPC call in order to generate a new session key:
```bash
curl -H "Content-Type: application/json" \
--data '{
"jsonrpc":"2.0",
"method":"author_rotateKeys",
"params":[],
"id":1
}' \
http://localhost:9934
```
The output of the above command should be something like:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": "0xcc038816bd81c238bd1d163c48cea9c5e3b62899b8f193863f68268a719cca44"
}
```
> **_WARNING:_** If the node exposes the RPC port to the internet, directly or through a reverse proxy, please make sure to restart the node with the argument `--rpc-methods safe` so that RPC methods related to the collator keys cannot be called anymore. For more information, see the [RPC Deployment](https://paritytech.github.io/devops-guide/guides/rpc_index.html?#important-flags-for-running-an-rpc-node) page of the Parachain Devops guide.
Next, submit the extrinsic `session.setKeys` with the collator account to the Acurast Canary chain. The first parameter of the extrinsic call is the `result` hex string in the output of the previous RPC call.
Any tool can be used to submit the extrinsic call, the important thing is that it is submitted by the collator account.
One option is to use the [polkadotjs UI web app](https://dotapps-io.ipns.dweb.link/?rpc=wss%3A%2F%2Fpublic-rpc.canary.acurast.com#/extrinsics):
The screenshot above show how to call the `session.setKeys` extrinsic, where the first drop down menu selects the account submitting the extrinsic (in this example, the account comes from the PolkadotJS Chrome extension).
Then the `submit the following extrinsic` box, the `session` pallet and the `setKeys` extrinsic are selected. Finally, provide the arguments: first the key, which corresponds to the result of the previous RPC call, and a `0x00` for the proof argument.
## Register as candidate
Once the session key is registered, the collator can be registered as a candidate, this is done through the extrinsic `collatorSelection.registerAsCandidate`, as before, the important thing is that the extrinsic is submitted by the collator account:
If the extrinsic is submitted successfully, the collator node is now fully onboarded and should start authoring blocks within 6-12 hours.
---
## Deployment Pricing
The pricing model for `deployment` execution has evolved over time. This page describes how pricing worked in the legacy system and how it works in the proposed new system.
## Legacy Pricing System
In the legacy system, each processor independently advertised their price as part of their advertisement on the Acurast marketplace. A processor's advertisement included three pricing components:
- **Base fee per execution**: a fixed amount charged per execution slot, regardless of duration or resource use.
- **Fee per millisecond**: an amount charged per millisecond of execution duration.
When a matcher proposed a match between a `deployment` and a processor, the protocol calculated the processor's fee for one execution as:
$$\text{price} = \max(f_{ms},\ f_{ms}^{min}) \times d + f_{base}$$
where:
- $f_{ms}$ is the processor's advertised fee per millisecond
- $f_{ms}^{min}$ is the global minimum fee per millisecond
- $d$ is the execution duration in milliseconds
- $f_{base}$ is the base fee per execution
The price was validated against the `deployment`'s declared reward: if the processor's calculated price exceeded the reward the developer had specified, the match was rejected.
Matchers were paid immediately when the matching transaction was included on chain, before any processor had acknowledged or executed the `deployment`. Their reward was a percentage of the unspent reward,
the portion of the developer's budget that remained after subtracting the sum of all processor prices.
A platform fee was deducted from the matcher's reward before the net amount was transferred.
## New Dynamic Pricing System
The new system replaces processor advertised pricing with **network derived pricing**.
Instead of each processor declaring their own rates, a processor's price is computed from their contribution to the benchmark rewards as tracked by the Acurast compute pallet.
### Price Per Execution
A processor's price per execution is derived through the following:
**Reward contribution per millisecond:**
The compute pallet tracks each processor's `reward_contribution` per epoch (a fixed number of blocks, approximately 90 minutes).
This represents the total reward the processor contributes to the benchmark rewards. The price per millisecond is obtained by dividing `reward_contribution` by the epoch length and block time:
$$p_{ms} = \frac{R_{epoch}}{N_{epoch} \times D_{slot}}$$
where:
- $R_{epoch}$ is the processor's reward contribution per epoch
- $N_{epoch}$ is the epoch length in blocks (900)
- $D_{slot}$ is the block time in milliseconds (6000 ms)
**Price per execution:**
$$\text{price} = \max\left(m \times p_{ms} \times d,\ p_{min}\right)$$
where:
- $m$ is the global price multiplier, this will start as a fixed point number adaptable by governance, and will eventually move to a curve based on the actual usage of the network
- $d$ is the execution duration in milliseconds as declared in the `deployment` schedule
- $p_{min}$ is the global minimum price per execution
The multiplier $m$ and the minimum price $p_{min}$ are protocol level parameters adjustable by governance without a runtime upgrade.
When the Acurast Matcher matches a `deployment`, it computes the price for each candidate processor and checks that the price does not exceed the developer's declared reward per execution.
For `deployments` with multiple scheduled executions, the sum of all per execution prices across the entire schedule must also fit within the total budget locked by the developer at registration.
### Matcher Incentive
Matchers are paid on **processor acknowledgment**, not on matching. When a processor acknowledges an assignment, the protocol calculates the price difference for that assignment:
$$\Delta = r - \text{price}$$
where:
- $r$ is the developer's declared reward per execution
- $\text{price}$ is the processor's computed price per execution
10% of this difference, multiplied by the number of executions in the assignment, is paid to the matcher. A platform fee of 30% is deducted from the matcher's share before transfer. The result is:
- **Matcher** receives a percentage of the price difference. once the processor acknowledges.
- **Processor** has their computed price per execution burned from the deployment budget each time they submit a valid report.
Tying matcher payment to acknowledgment aligns the matcher's incentive with match quality, a match the processor rejects earns the matcher nothing.
## Motivation for the change
The legacy pricing model placed the burden of price discovery entirely on processors.
Each manager had to independently choose and update the advertised rates for every managed processor, which in practice meant prices rarely changed at all as most processor managers did not engage with the pricing mechanism.
The new system removes per processor price configuration entirely. Prices are derived automatically from each processor's on chain benchmark contribution, which is already tracked by the protocol.
---
## From Canary To Mainnet - Overview
# Acurast: Migration to Mainnet and Conversion from cACU to ACU
## Migration vs Conversion
### Migration
Migration describes the process of moving funds and processors from Canary (cACU) to Mainnet (ACU). The migration window for cACU tokens is 90 days from when Mainnet goes live. After these 90 days, tokens can NEVER again be migrated from Canary to Mainnet. Processors can always be migrated from Canary to Mainnet (but never back).
:::warning Attention
**cACU tokens from Canary can only be migrated to Mainnet within the first 90 days after Mainnet start. After 90 days they can NEVER be migrated again to Mainnet. Plan accordingly!**
:::
### Conversion
Conversion describes the process of unlocking ACU tokens, which were created by migrating cACU from Canary to Mainnet, using a time-based linear ratio system. After migrating from Canary to Mainnet (which must occur within 90 days), users' tokens are locked for a minimum of approximately 84 days before they can begin the conversion unlock process.
The conversion ratio is directly proportional to the lock time elapsed: the percentage of maximum lock time completed equals the percentage of tokens received, creating a linear relationship. Any tokens not received due to early unlocking are sent to the treasury.
Importantly, conversion-locked tokens can be staked immediately to earn liquid rewards, though unstaking is required before triggering the final conversion unlock.
### Conversion Examples
Locked for the full duration: If the maximum lock time has passed (19353600 blocks, roughly 1344 days or 3.7 years) users receive 100 ACU for 100 conversion-locked ACU.
Locked for half of the maximum duration: If only half of the time has passed (9676800 blocks, roughly 672 days or 1.8 years) users receive 50 ACU for 100 conversion-locked ACU.
Locked for 10% of the maximum time: If only 10% of the maximum lock time has passed (1935360 blocks, roughly 134 days or 0.37 years) users get 10 ACU for 100 cACU
Locked for the minimum time of 84 days: If the minimum lock time of 84 days has passed (1209600 blocks) users get 6.25 ACU for 100 cACU.
| | Blocks | Seconds | Hours | ~ Days | ~ Months (30d) | ~ Years | Ratio % |
|---|---|---|---|---|---|---|---|
| **Max Duration** | 19353600 | 116121600 | 32256 | 1344 | 44.8 | 3.73 | **100** |
| **Half duration** | 9676800 | 58060800 | 16128 | 672 | 22.4 | 1.87 | **50** |
| **10% of Max Duration** | 1935360 | 11612160 | 3225.6 | 134.4 | 4.48 | 0.37 | **10** |
| **Min Duration** | 1209600 | 7257600 | 2016 | 84 | 2.8 | 0.23 | **6.25** |
### How to migrate and convert
1. User initiates migration on Acurast Canary on the [Acurast Hub](https://hub.acurast.com/)
2. All cACU, except for a minimum existential amount, are burned on Canary
3. ACU is assigned to the same account on Mainnet with a 1 to 1 ratio (e.g., 1000 cACU = 1000 ACU)
4. Users can decide to unlock their tokens at any time after the minimum lock time has passed (1209600 blocks, roughly 84 days)
5. Upon unlocking, a conversion ratio is applied that is relative to the time passed
6. User will keep the converted amount, while the remaining amount is sent to the treasury
### Conversion lock and Staking
Conversion-locked ACU can be staked in the Staked Compute Pool starting from day one of Mainnet. All staking rewards are fully claimable and transferable, they are not locked.
To trigger the conversion unlock, all tokens must first be completely unstaked. Please note that unstaking alone does not automatically remove the conversion lock.
#### Conversion with Staking Example:
A user has 1000 cACU. The total conversion lock time is roughly 3.7 years (44.8 months). They decide to stake with a cooldown period of 2 months.
After 20.4 months (672 days) the cooldown period is triggered by the user and then the two month cooldown period starts. After a total of 22 months the stake is finalized. However, the tokens are still conversion locked. The user can now decide to stake again, or to unlock the tokens and convert them. Since they went through half the duration, if they decide to unlock them completely, a ratio of 2:1 is applied and the user would receive 0.5 ACU for each 1 cACU of their conversion locked balance.
## Migration & Conversion FAQ
#### What happens on conversion unlock to the tokens which are not distributed to the user due to preliminary unlock?
These tokens are sent to the Treasury.
#### If I get staking rewards on my conversion locked tokens, are the rewards liquid after unstaking, while the conversion locked tokens are still locked?
Yes, the the staking rewards are liquid
#### Will my rewards from Base Benchmark Rewards and Staking Rewards for running processors be locked?
No. All rewards are liquid (unless you selected autocompound in Staking, then the staking rewards are added to your locked Stake).
#### What if I want my funds and processors on a different account on Mainnet than on Canary? Can I split up accounts?
Migration strictly happens between the exact same account on Canary as on Mainnet. If you want to transfer to different accounts you need to do that BEFORE the migration. Some limitations apply:
**Transfer Processors:** Processors from one account can only be migrated as a whole from Canary to Mainnet and they will be assigned to the same manager account as before. It is possible to transfer all processors on Canary from one account to another, but only ALL processors at once and only to a fresh and unused Acurast account. You cannot split up processors between accounts, you cannot "send" processors to existing accounts that already have processors or tokens.
**Transfer Tokens:** Tokens from one account can only be migrated as a whole from Canary to Mainnet and they will be assigned to the same manager account as before. But before the Migration function is activated, token transfers will also be activated on Canary. This means you can decide to split up funds between different Canary accounts BEFORE migrating them to mainnet. Once the funds are migrated, they will be locked and cannot move again, until they are unlocked. See Conversion about the locking mechanism that kicks in after migration.
#### What is the right order to do all of this?
1. Unstake your stakes on Canary. Make sure to do that BEFORE the 90 day window ends. Unstaking comes with a cooldown of up to two days!
2. Migrate your funds from Canary to Mainnet
3. Migrate your processors from Canary to Mainnet
---
## Transfers on Canary
:::info
Token transfers on Canary have been activated on Tuesday, November 11, 2025 - 5pm UTC
:::
Migration (the process of moving funds and processors from Canary to Mainnet) strictly happens between the exact same account on Canary as on Mainnet. If you want to transfer tokens or processors to different accounts you need to do that **BEFORE** the migration on Canary. Some limitations apply:
## Transfer Processors
Processors from one account can only be migrated as a whole from Canary to Mainnet and they will be assigned to the same manager account as before. It is possible to transfer all processors on Canary from one account to another, but only ALL processors at once and only to a fresh and unused Acurast Canary account. You cannot split up processors between accounts, you cannot "send" processors to existing accounts that already have processors or tokens.
## Transfer Tokens
Tokens from one account can only be migrated as a whole from Canary to Mainnet and they will be assigned to the same manager account as before. But before the Migration function is activated, **token transfers have been activated on Canary**. This means you can decide to split up funds between different Canary accounts BEFORE migrating them to Mainnet. Once the funds are migrated to Mainnet, they will be locked and cannot move again, until they are unlocked. See [Conversion](/acurast-protocol/from-canary-to-mainnet/overview) about the locking mechanism that kicks in after migration.
## Transfers and Impact on Stakes
:::warning Attention
**Do not transfer your processors to a different account if you have an active stake. You risk being slashed. Unstake first, wait for the cooldown to end, then transfer processors.**
:::
You must unstake and wait for the cooldown to end, if you want to transfer your processors to a different account, the account with the stake will suddenly lose all of its compute and you will be exposed to staking penalties. It's the same as if you would suddenly cut your internet connection.
You must unstake and wait for the cooldown to end, before you can migrate to Mainnet. As the cooldown can take up to 48 hours, you should absolutely trigger unstake before the migration window of 90 days ends.
## How to Transfer Funds on Canary
There are two ways how you can transfer cACU tokens on Canary:
**A) Use your Substrate based wallet**
1. Use your Substrate based wallet (e.g. Talisman, SubWallet)
2. Make sure you see the token in your account (check [Wallet page](/token-holders/wallets/wallet-overview) for how-to)
3. Transfer as usual: select token, enter target address and amount, send
4. Sign with your wallet
**B) Use the transfer function on [hub.acurast.com](https://hub.acurast.com) (e.g. using Metamask or walletconnect enabled wallets)**
1. Go to the balance section of the hub
2. Click **Transfer**
3. Enter amount and target address
4. Click **Transfer** and sign with your wallet
**Remember, tokens which are locked by staking cannot be transferred.**
## How to Transfer Processors on Canary
**DO NOT TRANSFER PROCESSORS IF YOU ARE STAKING! REALLY!**
1. Go to the Phones section on [hub.acurast.com](https://hub.acurast.com)
2. **Toggle Advanced** on top right
3. In Processor Ownership click **Transfer**
4. Carefully read what it says on the screen
5. Enter your target address
6. Double check your target address to ensure it's yours
7. Click **Transfer Ownership**
Remember, you can only transfer processors to fresh unused accounts with 0 balance. Otherwise it won't work. You can also only transfer all processors at once.
---
## Governance
Acurast Mainnet governance is implemented through a combination of [`pallet-referenda`](https://docs.rs/pallet-referenda/latest/pallet_referenda/) and [`pallet-conviction-voting`](https://docs.rs/pallet-conviction-voting/latest/pallet_conviction_voting/).
Together they provide an on chain referendum system where ACU holders can vote on protocol changes, with voting power amplified by voluntarily locking ACUs for longer periods.
## Overview
Governance on Acurast Mainnet follows the OpenGov model: a referendum is created, a decision deposit is placed, and ACU holders vote during a fixed decision window.
A referendum passes when both an approval threshold and a support threshold are met simultaneously for a continuous confirmation period.
Referenda are organized into tracks, where each track defines its own voting periods, deposit amounts, approval and support thresholds, and the set of origins allowed to submit to it.
Currently there is a single track (the Root track) which gates all privileged protocol operations such as runtime upgrades and parameter changes.
In the future, additional tracks with more granular permission levels will be introduced, allowing any ACU holder to submit referenda for the appropriate scope of actions.
In the current configuration only council members can submit new referenda.
## Referenda Lifecycle
Every referendum goes through the following stages:
1. **Submission**: A proposal is submitted and is placed in a queue for the selected track.
2. **Preparing**: The referendum waits for the track's preparation period. During this time a decision deposit must be placed by any account, or the referendum will be discarded after the undeciding timeout period.
3. **Deciding**: Voting is open for the track's decision period. The referendum is evaluated continuously against the approval and support thresholds of the track.
4. **Confirming**: Once both thresholds are met, the referendum must remain passing for the track's confirmation period. If it drops below the thresholds during this window the confirmation period resets.
5. **Enactment**: After confirmation the approved call is scheduled for execution after a minimum delay of the track's min enactment period.
See the [Root Track Parameters](#root-track-parameters) section for the specific timelines of the Root track.
## Conviction Voting
ACU holders vote using [`pallet-conviction-voting`](https://docs.rs/pallet-conviction-voting/latest/pallet_conviction_voting/).
Each vote is cast as *aye*, *nay*, or *abstain*, with an optional conviction level that multiplies voting power in exchange for a ACU lock after the referendum concludes.
| Conviction | Vote multiplier | Lock duration |
|------------|-----------------|---------------|
| None | 0.1× | No lock |
| 1× | 1× | 7 days |
| 2× | 2× | 14 days |
| 3× | 3× | 28 days |
| 4× | 4× | 56 days |
| 5× | 5× | 112 days |
| 6× | 6× | 224 days |
ACUs are locked immediately when a vote is cast. If the referendum ends and the vote was on the winning side with conviction > 0, the lock continues for the conviction period after the referendum ends.
For losing side votes the lock is released once the referendum concludes. Releasing the lock always requires an explicit `unlock` call. An account can have at most 512 concurrent votes across all active referenda.
## Root Track Parameters
Acurast Mainnet has a single governance track, the root track (ID 0), which gates all privileged protocol operations such as runtime upgrades and parameter changes.
| Parameter | Value |
|----------------------|---------|
| Max deciding at once | 1 |
| Prepare period | 12 hours |
| Decision period | 48 hours |
| Confirm period | 6 hours |
| Min enactment period | 2 hours |
| Undeciding timeout | 14 days |
| Vote locking period | 7 days |
## Approval and Support Thresholds
Passing the deciding stage requires two independent thresholds to be satisfied at the same time.
**Approval** measures the share of *aye* votes out of all conviction weighted votes cast (aye + nay).
The root track uses a reciprocal curve: approval starts at 100% at the beginning of the decision period and decreases toward 50% as the decision period progresses, with the sharpest drop in the first few hours.
**Support** measures the total raw ACU balance behind aye votes (pre-conviction) as a fraction of the active ACU issuance.
The root track uses a linear curve: the required support starts at 50% at the beginning of the decision period and decreases linearly to 0% by the end.
Both thresholds must be satisfied simultaneously for the entire track's confirmation period before the referendum is approved.
## How to Vote
Currently there are 2 ways a user can vote.
- Through the [Acurast Hub Governance](https://hub.acurast.com/governance) page. When a referendum is ongoing, it will appear on the Hub's governance page and users will be able to cast a vote and, later on, unlock ACUs from previous votes.
- Through the [Polkadot JS Web Application](https://polkadot.js.org/apps//?rpc=wss%3A%2F%2Fpublic-rpc.mainnet.acurast.com#/referenda).
Once the vote is cast, it can be updated by voting again. This allows the user to adjust both the ACU amount and conviction parameter.
---
## Networks
A list of all related core infrastrucuture for each deployed Acurast network. More details on the functionalities of each network can be found in the [Protocol Architecture ↗](/acurast-protocol/architecture/instances)
## Acurast Mainnet
| | Details |
| -------- | ---------------------------------------------------------------- |
| Token | ACU |
| RPC | [wss://public-rpc.mainnet.acurast.com](wss://public-rpc.mainnet.acurast.com) |
| Explorer | [Block Explorer](https://acurastbot.com/explorer) |
## Acurast Canary
| | Details |
| -------- | ------------------------------------------------------------------------------------------------------------- |
| Token | cACU |
| RPC | [wss://public-rpc.canary.acurast.com](wss://public-rpc.canary.acurast.com) |
| Explorer | [Block Explorer](https://canary.acurastbot.com/explorer) |
| Faucet | [faucet.acurast.com](https://faucet.acurast.com/) |
## Acurast Devnet
| | Details |
| -------- | ------------------------------------------------------------------------------------------------------ |
| Token | dACU |
| RPC | [wss://acurast-devnet-ws.prod.gke.papers.tech](wss://acurast-devnet-ws.prod.gke.papers.tech) |
| Explorer | [Block Explorer](https://polkadot.js.org/apps/?rpc=wss://acurast-devnet-ws.prod.gke.papers.tech#/explorer) |
:::note
Public RPC nodes might be rate-limited. If you run into issues for your use case, please reach out in the community channels.
:::
---
## Node Setup
The recommended way to run the Acurast node is by using the published Docker images for [Mainnet](https://hub.docker.com/r/acurast/node-mainnet/tags) and [Canary](https://hub.docker.com/r/acurast/node-canary/tags).
## Get the chain spec
Download the Acurast chain spec from the Acurast GitHub repository:
- [Acurast Canary chain spec](https://github.com/Acurast/acurast-substrate/blob/acurast-v0.21.0/chain-specs/acurast-kusama-parachain-2239-raw.json)
- [Acurast Mainnet chain spec](https://github.com/Acurast/acurast-substrate/blob/acurast-v0.21.0/chain-specs/acurast-mainnet-parachain-3396-raw.json)
## Configure the node
Create an `acurast-node` folder. Inside this folder, the following 2 folders and file:
- `chain-specs` - place the downloaded chain spec here
- `data` - this is where the node will store its data
- `docker-compose.yml` - this is where the docker-compose configuration will be placed
In the `docker-compose.yml` file, put the following content:
For Acurast Canary:
```yml
services:
node:
image: "acurast/node-canary:acurast-v0.25.5"
command: "--chain /node/chain-specs/acurast-kusama-parachain-2239-raw.json \
--base-path /node/data \
--bootnodes /ip4/57.129.99.69/tcp/30334/ws/p2p/12D3KooWKrSDeVQ4tVQ1eGjqVAhAW3cgMQFHNCBbJrpmupEvdD4A \
--port 30334 \
--rpc-port 9934 \
--rpc-external \
--rpc-methods safe \
--rpc-cors all \
--name MyNode \ # choose an appropriate name here
--telemetry-url \"wss://telemetry.polkadot.io/submit/ 0\" \
--database=rocksdb \
--pruning=archive"
ports:
- "30334:30334"
- "9934:9934"
volumes:
- ./:/node
logging:
options:
max-size: "10m"
max-file: "3"
```
For Acurast Mainnet:
```yml
services:
node:
image: "acurast/node-mainnet:acurast-v0.25.5"
command: "--chain /node/chain-specs/acurast-mainnet-parachain-3396-raw.json \
--base-path /node/data \
--bootnodes /ip4/82.220.91.112/tcp/30335/ws/p2p/12D3KooWMJM3htCon6tQ6FzRuWkxtwEkd3i5awZitdTviWwJX3KY \
--port 30334 \
--rpc-port 9934 \
--rpc-external \
--rpc-methods safe \
--rpc-cors all \
--name MyNode \ # choose an appropriate name here
--telemetry-url \"wss://telemetry.polkadot.io/submit/ 0\" \
--database=rocksdb \
--pruning=archive"
ports:
- "30334:30334"
- "9934:9934"
volumes:
- ./:/node
logging:
options:
max-size: "10m"
max-file: "3"
```
The configuration above will start the Acurast node with the following options:
- `--chain` - specifies the chain spec file
- `--base-path` - specifies the base path for the node data
- `--bootnodes` - specifies the bootnodes to connect to
- `--port` - specifies the p2p port for the node
- `--rpc-port` - specifies the RPC port for the node
- `--rpc-external` - allows external access to the RPC interface
- `--rpc-methods safe` - allows only safe RPC methods
- `--rpc-cors all` - allows all CORS requests
- `--name` - the name of the node
- `--telemetry-url` - The telemetry URL, the node will send telemetry data to [telemetry.polkadot.io](https://telemetry.polkadot.io/#/0xce7681fb12aa8f7265d229a9074be0ea1d5e99b53eedcec2deade43857901808) under the configured `name`
- `--database=rocksdb` - specifies the database type
- `--pruning=archive` - specifies the pruning mode, change `archive` to the number of blocks to keep if you want to prune the database
## Start the node
In `acurast-node` folder and run the following command:
```bash
docker compose up -d
```
This will start the Acurast node in detached mode. You can check the logs by running:
```bash
docker compose logs -f
```
## Run a Collator
A node can be turned into a collator configuring its identity. One way to do so is to use a tool like [subkey](https://docs.rs/crate/subkey/latest).
```bash
subkey generate
```
The output of the above command is something like:
```
Secret phrase:
Network ID: substrate
Secret seed: 0x4f....
Public key (hex): 0x86....
Account ID: 0x86....
Public key (SS58): 5F7Cm8Kt57dX3SkdtYYDGdMn3yiPvC8dr3oSratmGjjLmSss
SS58 Address: 5F7Cm8Kt57dX3SkdtYYDGdMn3yiPvC8dr3oSratmGjjLmSss
```
Then configure the node with the generated key by calling the following RPC:
```bash
curl -H "Content-Type: application/json" \
--data '{
"jsonrpc":"2.0",
"method":"author_insertKey",
"params":[
"aura",
"INSERT_SECRET_PHRASE",
"INSERT_PUBLIC_KEY_HEX_FORMAT"
],
"id":1
}' \
http://localhost:9934
```
In order for the above call to succeed, the node needs to be started with `--rpc-methods unsafe`. Once the key has been registered, the node can be restarted with the `--collator` flag.
The node is now setup as a collator, but it will not start authoring blocks yet. At this stage, the Acurast team manages the list of collators that can actually author blocks.
## Collator Onboarding
### Pre registration checks
- Make sure the node is setup with an identity as described above.
- Make sure the node is fully synced.
- Make sure the node was started with the `--collator` flag.
- Make sure the node is running on hardware that meet the minimum requirements. It is possible to check by looking at the node logs when it first starts:
If the hardware is good enough, there should not be any warning log message after the benchmarks logs shown in the screenshot above.
### Set session key
A session key needs to be registered. To do that, first perform the `author_rotateKeys` RPC call in order to generate a new session key:
```bash
curl -H "Content-Type: application/json" \
--data '{
"jsonrpc":"2.0",
"method":"author_rotateKeys",
"params":[],
"id":1
}' \
http://localhost:9934
```
The output of the above command should be something like:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": "0xcc038816bd81c238bd1d163c48cea9c5e3b62899b8f193863f68268a719cca44"
}
```
> **_WARNING:_** If the node exposes the RPC port to the internet, directly or through a reverse proxy, please make sure to restart the node with the argument `--rpc-methods safe` so that RPC methods related to the collator keys cannot be called anymore. For more information, see the [RPC Deployment](https://paritytech.github.io/devops-guide/guides/rpc_index.html?#important-flags-for-running-an-rpc-node) page of the Parachain Devops guide.
Next, submit the extrinsic `session.setKeys` with the collator account to the Acurast Canary or Acurast Mainnet chain. The first parameter of the extrinsic call is the `result` hex string in the output of the previous RPC call.
Any tool can be used to submit the extrinsic call, the important thing is that it is submitted by the collator account.
One option is to use the polkadotjs UI web app:
- [Acurast Canary](https://dotapps-io.ipns.dweb.link/?rpc=wss%3A%2F%2Fpublic-rpc.canary.acurast.com#/extrinsics)
- [Acurast Mainnet](https://dotapps-io.ipns.dweb.link/?rpc=wss%3A%2F%2Fpublic-rpc.mainnet.acurast.com#/extrinsics)
The screenshot above show how to call the `session.setKeys` extrinsic, where the first drop down menu selects the account submitting the extrinsic (in this example, the account comes from the PolkadotJS Chrome extension).
Then the `submit the following extrinsic` box, the `session` pallet and the `setKeys` extrinsic are selected. Finally, provide the arguments: first the key, which corresponds to the result of the previous RPC call, and a `0x00` for the proof argument.
### Register as candidate
Once the session key is registered, the collator can be registered as a candidate, this is done through the extrinsic `collatorSelection.registerAsCandidate`, as before, the important thing is that the extrinsic is submitted by the collator account:
If the extrinsic is submitted successfully, the collator node is now fully onboarded and should start authoring blocks within 6-12 hours.
---
## Node Tunnel Setup
## Deploying a node with the Acurast Tunnel service
This document walks through running an Acurast Node with the built-in Acurast
Tunnel service enabled. It is self-contained: every CLI flag, port, cert mode,
and proxy rule needed to bring a tunnel enabled node is described inline.
### Overview
The tunnel service relays end-user TCP traffic to Acurast processors. Clients
(processors) open a long lived control connection to the node, over QUIC by
default, or HTTP/2-over-TLS if QUIC is blocked, and the node forwards public
end user TCP byte streams to a registered processor based on the SNI sent by
the end user.
The server owns **four ports**:
| Port (default) | Protocol | Role |
| --- | --- | --- |
| `4433` | UDP (QUIC) | Processor (agent) connections — primary |
| `4433` | TCP (HTTP/2 + TLS) | Processor connections — fallback when QUIC is blocked |
| `443` | TCP | ACME TLS-ALPN-01 challenge port and Public end-user connections (SNI-routed to a registered agent) |
Auth is bound to on-chain state: a client is accepted only if its mTLS cert
carries an `(AcurastJobId, processor_id)` extension that resolves to a valid
`StoredMatches` entry in `pallet-acurast-marketplace`, **and** if the cert's
P-256 public key appears in that assignment's `pub_keys`.
### Prerequisites
- A running Acurast Node, see [Node Setup](./node-setup).
- DNS A record for the relay hostname (e.g. `relay.acurast.example.com`)
pointing at the host's public IP.
- TCP port 443 reachable from the public internet, with **no TLS terminator**
intercepting it for the relay SNI (see "Behind an nginx reverse proxy"
below).
### CLI flags
All flags below take effect only when `--tunnel` is also set.
| Flag | Default | Purpose |
| --- | --- | --- |
| `--tunnel` | _off_ | Enable the integrated tunnel server. Required for any other `--tunnel-*` flag to take effect. |
| `--tunnel-bind-addr ` | `0.0.0.0` | IP the tunnel listeners bind to. |
| `--tunnel-api-port ` | `4433` | QUIC (UDP) + HTTP/2-over-TLS (TCP) port for processor (agent) connections. |
| `--tunnel-port ` | `443` | TLS-ALPN-01 challenge port. **Must be reachable as port 443** (or have port 443 stream proxied to it, _not_ TLS terminated). Also, public TCP port for end user connections. The server peeks SNI and routes raw bytes to a registered agent. |
| `--tunnel-cert-path ` | _none_ | Path to the server's PEM certificate chain. This is where the provisioned cert is written and re-read. |
| `--tunnel-key-path ` | _none_ | Path to the PEM private key matching `--tunnel-cert-path`. |
| `--tunnel-acme-domain ` | _none_ | Domain to provision via ACME TLS-ALPN-01 (e.g. `relay.example.com`). |
| `--tunnel-acme-creds-path ` | `server_acme_creds.json` | Path used to persist ACME account credentials between runs. |
| `--tunnel-acme-renew-days ` | `30` | Trigger background ACME renewal this many days before expiry. |
## Listener roles
| Listener | Port flag | Behavior |
| --- | --- | --- |
| **ALPN**, **Public** | `--tunnel-port` (TCP) | Peeks SNI. If the SNI matches the server's own ACME challenge domain, completes the TLS-ALPN-01 challenge locally; otherwise proxies bytes through the registered tunnel client (so the client can complete _its own_ ACME challenge). Also, accepts raw TCP from end users, SNI routed to a registered agent. |
| **QUIC** | `--tunnel-api-port` (UDP) | Accepts QUIC connections from agents. |
| **H2 + TLS** | `--tunnel-api-port` (TCP) | Accepts HTTP/2-over-TLS connections from agents (used when QUIC is blocked). |
> If `--tunnel-port` is not 443, port 443 must be forwarded to it via a
> **TCP stream proxy** (e.g. nginx `stream {}` module). A TLS-terminating
> proxy (nginx `http {}`) will break the ACME challenge and end-user tunnel
> TLS.
## Docker Compose deployment
Use the following docker image:
`acurast/node-mainnet:acurast-v0.26.2`
`acurast/node-canary:acurast-v0.26.2`
An example `docker-compose.yml` for a Acurast Tunnel enabled deployment:
```yaml
services:
node:
image: acurast/node-mainnet:acurast-v0.26.2
restart: always
command: "--chain /node/chain-specs/acurast-mainnet-parachain-3396-raw.json \
--base-path /node/data \
--bootnodes /ip4/82.220.91.112/tcp/30335/ws/p2p/12D3KooWMJM3htCon6tQ6FzRuWkxtwEkd3i5awZitdTviWwJX3KY \
--port 30334 \
--rpc-port 9934 \
--rpc-external \
--rpc-methods safe \
--rpc-cors all \
--name MyNode \ # choose an appropriate name here
--telemetry-url \"wss://telemetry.polkadot.io/submit/ 0\" \
--database=rocksdb \
--pruning=archive \
--tunnel \
--tunnel-acme-domain relay.acurast.example.com \
--tunnel-cert-path /node/acme/server_cert.pem \
--tunnel-key-path /node/acme/server.key \
--tunnel-acme-creds-path /node/acme/server_acme_creds.json"
ports:
# Substrate p2p
- '30334:30334'
# JSON-RPC
- '9934:9934'
# Tunnel: processor API (QUIC needs UDP; H2 fallback is TCP)
- '4433:4433/tcp'
- '4433:4433/udp'
# Tunnel: ACME TLS-ALPN-01 — published as host :443 when the node owns 443.
# Behind an nginx reverse proxy, publish a different host port (e.g. 6443)
# and let nginx own 443 (see the next section).
- '443:443'
volumes:
# Chain data + certs in a single project-relative directory.
- ./:/node
logging:
options:
max-size: "10m"
max-file: "3"
```
```yaml
services:
node:
image: acurast/node-canary:acurast-v0.26.2
restart: always
command: "--chain /node/chain-specs/acurast-kusama-parachain-2239-raw.json \
--base-path /node/data \
--bootnodes /ip4/57.129.99.69/tcp/30334/ws/p2p/12D3KooWKrSDeVQ4tVQ1eGjqVAhAW3cgMQFHNCBbJrpmupEvdD4A \
--port 30334 \
--rpc-port 9934 \
--rpc-external \
--rpc-methods safe \
--rpc-cors all \
--name MyNode \ # choose an appropriate name here
--telemetry-url \"wss://telemetry.polkadot.io/submit/ 0\" \
--database=rocksdb \
--pruning=archive \
--tunnel \
--tunnel-acme-domain relay.acurast.example.com \
--tunnel-cert-path /node/acme/server_cert.pem \
--tunnel-key-path /node/acme/server.key \
--tunnel-acme-creds-path /node/acme/server_acme_creds.json"
ports:
# Substrate p2p
- '30334:30334'
# JSON-RPC
- '9934:9934'
# Tunnel: processor API (QUIC needs UDP; H2 fallback is TCP)
- '4433:4433/tcp'
- '4433:4433/udp'
# Tunnel: ACME TLS-ALPN-01 — published as host :443 when the node owns 443.
# Behind an nginx reverse proxy, publish a different host port (e.g. 6443)
# and let nginx own 443 (see the next section).
- '443:443'
volumes:
# Chain data + certs in a single project-relative directory.
- ./:/node
logging:
options:
max-size: "10m"
max-file: "3"
```
### Volume layout
The `./:/node` mount means the working directory contains, after first
startup:
```
./
├── chain-specs/...
├── data/ # substrate database
└── acme/
├── server_cert.pem # written by ACME provisioner
├── server.key # written by ACME provisioner
└── server_acme_creds.json # persisted ACME account
```
## Behind an nginx reverse proxy
If the host already runs nginx on port 443 (e.g. terminating TLS for other
websites), the tunnel cannot also bind 443 directly. Instead, publish the
tunnel container's port 443 on a non-standard host port (e.g. `6443`) and let
nginx route SNI to it via the **stream** module with `ssl_preread`.
### Compose change
```yaml
ports:
- '6443:443/tcp' # was '443:443/tcp'
```
(All other tunnel port mappings stay the same.)
### nginx.conf
```nginx
stream {
# Route incoming :443 connections by SNI without decrypting them.
map $ssl_preread_server_name $upstream {
hostnames;
# explicitly route the relay hostname to the node
relay.acurast.example.com acurast_tunnel;
# route existing domains that need to terminate TLS
*.my-existing-domain.com https_terminator;
# all the rest are routed to the node (Acurast deployments trying to setup a tunnel with custom domains)
default acurast_tunnel;
}
upstream acurast_tunnel {
# docker-compose published the container's :443 as host :6443.
server 127.0.0.1:6443;
}
upstream https_terminator {
# Whatever local nginx http {} server handles your other sites.
server 127.0.0.1:7443;
}
server {
listen 443;
listen [::]:443;
proxy_pass $upstream;
ssl_preread on;
}
}
# Your existing http {} block handles non-tunnel SNI on 127.0.0.1:7443.
http {
# ... standard config ...
server {
# here we need to listen on 7443
listen 7443 ssl;
server_name rpc.my-existing-domain.com;
ssl_certificate /etc/letsencrypt/live/rpc.my-existing-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/rpc.my-existing-domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://127.0.0.1:9934;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
}
```
## Verification
Expected log sequence on a clean first start:
```
ROUTER: API 0.0.0.0:4433 | PUB 0.0.0.0:8443 | ALPN 0.0.0.0:443
TLS: cert at /node/certs/server_cert.pem is expired or missing, provisioning via ACME
ACME: provisioning server cert for relay.acurast.example.com
ACME: provisioned cert valid until
```
---
## Whitepapers
## Acurast Whitepaper
**Published:** March 17, 2025
The original Acurast whitepaper introducing the decentralized compute network powered by smartphones and Trusted Execution Environments (TEEs).
[Read on arXiv →](https://arxiv.org/abs/2503.15654)
---
## MiCA Whitepaper
**Published:** February 21, 2025
The MiCA (Markets in Crypto-Assets) whitepaper is Acurast's regulatory compliance document for the European Union's crypto-asset regulation. It provides detailed disclosure about the ACU token, its utility, governance, risks, and compliance with EU regulatory requirements.
[Read the whitepaper →](https://docsend.com/view/vxyaenm9z2mfghg9)
---
## Cargo Runtime Environment
Deployments in the Cargo runtime run as native binaries inside a Linux distro image on the processor, isolated via PRoot. Standard system environment variables are available directly. Host services - deployment metadata, cryptographic signing, and browser control - are accessed through an RPC API using JSON-RPC 2.0 over an abstract Unix domain socket.
## Distro Image
Each Cargo deployment must specify a Linux distro image in its manifest, along with the SHA256 hash of the image for verification. The processor downloads and extracts the image before running the deployment.
```json title=manifest.json
{
"version": "1",
"entrypoint": "example.sh",
"image": {
"url": "https://example.com/distro.tar.xz",
"sha256": "abc123..."
}
}
```
Supported distro images can be found in the [Termux proot-distro](https://github.com/termux/proot-distro) repository.
## Environment Variables
Env vars declared in your deployment config are injected as standard system environment variables. See [Environment Variables](/developers/build/environment-variables) for how to declare them.
```rust
let api_key = std::env::var("API_KEY").unwrap();
```
## PRoot Quirks
The PRoot container starts with a minimal environment. The processor injects the following defaults before the entrypoint runs:
| Variable / file | Default value |
| --- | --- |
| `PATH` | `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin` |
| `HOME` | `/root` |
| `/etc/resolv.conf` | Google (`8.8.8.8`, `8.8.4.4`), Cloudflare (`1.1.1.1`, `1.0.0.1`), Quad9 (`9.9.9.9`) - skipped if the file already contains a non-loopback nameserver |
These can be overridden in your entrypoint script if your deployment requires different values.
### DNS resolution
Android has no system-level `/etc/resolv.conf`. Without it, DNS resolution inside the chroot fails silently - `apt-get update` downloads nothing, package lists stay empty, and subsequent `apt-get install` calls report packages as not found. The processor writes a set of public resolvers to `/etc/resolv.conf` at prepare time to avoid this, unless the file already contains a non-loopback nameserver entry.
To use different resolvers, write them yourself before any network calls:
```sh
echo "nameserver " > /etc/resolv.conf
```
### Network interfaces (`getifaddrs`)
Programs running inside the chroot that call `getifaddrs()` - SSH daemons, some package managers - receive Android's real network interfaces (`wlan0`, `rmnet_data0`, etc.) instead of a standard Linux interface list. This happens because PRoot bind-mounts `/proc` from the Android host and glibc queries the kernel via netlink, which responds with Android interfaces. Programs may fail to bind or behave unexpectedly as a result.
To fix this, override `getifaddrs` and `freeifaddrs` in a shared library and preload it via `LD_PRELOAD`:
```c title="getifaddrs_override.c"
#include
#include
#include
#include
#include
#include
int getifaddrs(struct ifaddrs **ifap) {
struct ifaddrs *ifa = calloc(1, sizeof(struct ifaddrs));
if (!ifa) return -1;
ifa->ifa_next = NULL;
ifa->ifa_name = strdup("lo");
ifa->ifa_flags = IFF_UP | IFF_RUNNING | IFF_LOOPBACK;
struct sockaddr_in *addr = calloc(1, sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = htonl(0x7f000001);
ifa->ifa_addr = (struct sockaddr *)addr;
struct sockaddr_in *netmask = calloc(1, sizeof(struct sockaddr_in));
netmask->sin_family = AF_INET;
netmask->sin_addr.s_addr = htonl(0xff000000);
ifa->ifa_netmask = (struct sockaddr *)netmask;
*ifap = ifa;
return 0;
}
void freeifaddrs(struct ifaddrs *ifa) {
while (ifa) {
struct ifaddrs *next = ifa->ifa_next;
free(ifa->ifa_name);
free(ifa->ifa_addr);
free(ifa->ifa_netmask);
free(ifa);
ifa = next;
}
}
```
Compile and preload it before starting the affected program:
```sh
gcc -shared -fPIC -o /usr/local/lib/getifaddrs_override.so getifaddrs_override.c
export LD_PRELOAD=/usr/local/lib/getifaddrs_override.so
```
---
## RPC API
The processor injects a `BRIDGE_SOCKET` environment variable at runtime containing the name of an abstract Unix socket. To call a host API, open a new socket connection, send a single [JSON-RPC 2.0](https://www.jsonrpc.org/specification) request line, and read back the response line. Each connection carries exactly one request/response exchange.
```
socket address: \0 (abstract namespace - null-byte prefix)
protocol: JSON-RPC 2.0, newline-delimited, one call per connection
```
---
### Processor
Android: 1.25.0+
#### `processor_version`
Returns the version of the processor runtime.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "processor_version",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"version": string
},
"id": string
}
```
---
### Deployment
Android: 1.25.0+
#### `deployment_id`
Returns the identifier of the active deployment.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "deployment_id",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"id": string,
"origin": {
"kind": string,
"source": string // hex
}
},
"id": string
}
```
---
Android: 1.25.0+
#### `deployment_ipfsHash`
Returns the IPFS CID of the deployment app.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "deployment_ipfsHash",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"ipfsHash": string
},
"id": string
}
```
---
Android: 1.25.0+
#### `deployment_slot`
Returns the execution slot index assigned to this processor, or `null` if no slot is assigned.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "deployment_slot",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"slot": number | null
},
"id": string
}
```
---
Android: 1.25.0+
#### `deployment_publicKeys`
Returns the signing public keys of this processor for the active deployment, keyed by curve. Only curves for which a key exists are included.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "deployment_publicKeys",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"publicKeys": {
"p256"?: string, // hex
"secp256k1"?: string, // hex
"ed25519"?: string // hex
}
},
"id": string
}
```
---
Android: 1.25.0+
#### `deployment_encryptionKeys`
Returns the ECDH encryption public keys of this processor for the active deployment. Supported curves: `p256`, `secp256k1`.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "deployment_encryptionKeys",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"encryptionKeys": {
"p256"?: string, // hex
"secp256k1"?: string // hex
}
},
"id": string
}
```
---
Android: 1.25.0+
#### `deployment_assignedProcessors`
Returns all processors assigned to this deployment, keyed by their SS58 address, along with their public keys per curve.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "deployment_assignedProcessors",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"processors": {
[ss58: string]: {
"p256"?: string, // hex, signing key
"secp256k1"?: string, // hex, signing key
"ed25519"?: string, // hex, signing key
"encP256"?: string, // hex, encryption key
"encSecp256k1"?: string // hex, encryption key
}
}
},
"id": string
}
```
---
### Signer
Supported curves: `"p256"`, `"secp256k1"`, `"ed25519"`. Encryption and decryption support `"p256"` and `"secp256k1"` only.
Android: 1.25.0+
#### `signer_publicKey`
Returns the public key for the given curve. Pass `derivationPath` for HD key derivation (supported on `secp256k1`).
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "signer_publicKey",
"params": [{
"curve": "p256" | "secp256k1" | "ed25519",
"derivationPath"?: string
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"publicKey": string, // hex
"derivationPath"?: string // present only if requested
},
"id": string
}
```
---
Android: 1.25.0+
#### `signer_sign`
Signs a hex-encoded byte string with the given curve key. Pass `derivationPath` for HD signing.
:::warning ECDSA curves sign a digest, not a message
For `p256` and `secp256k1` the input is **not hashed**: the processor performs a raw ECDSA signature over exactly the bytes you pass. Hash your message first (usually SHA-256) and pass the 32-byte digest. The result is raw `r || s` (64 bytes), not DER; `secp256k1` signatures are low-`s` normalized with no recovery byte. `ed25519` hashes internally, so pass the full message. See [Signing requests from a deployment](/developers/examples/signing-requests).
:::
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "signer_sign",
"params": [{
"curve": "p256" | "secp256k1" | "ed25519",
"bytes": string, // hex
"derivationPath"?: string
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"bytes": string // hex
},
"id": string
}
```
---
Android: 1.25.0+
#### `signer_encrypt`
Encrypts bytes using ECDH key agreement with the receiver's public key. Supported curves: `p256`, `secp256k1`.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "signer_encrypt",
"params": [{
"curve": "p256" | "secp256k1",
"publicKey": string, // hex, receiver's public key
"salt": string, // hex
"bytes": string // hex, plaintext
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"bytes": string // hex, ciphertext
},
"id": string
}
```
---
Android: 1.25.0+
#### `signer_decrypt`
Decrypts bytes using ECDH key agreement with the sender's public key. Supported curves: `p256`, `secp256k1`.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "signer_decrypt",
"params": [{
"curve": "p256" | "secp256k1",
"publicKey": string, // hex, sender's public key
"salt": string, // hex
"bytes": string // hex, ciphertext
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"bytes": string // hex, plaintext
},
"id": string
}
```
---
### Browser
These methods control an embedded WebView on the processor device and are only available on Android processors.
Android: 1.25.0+
#### `browser_debugUrl`
Returns the Chrome DevTools remote debugging URL for the WebView.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_debugUrl",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"url": string
},
"id": string
}
```
---
Android: 1.27.0
#### `browser_initSurface`
Sets the size of the surface the browser paints its pages to, applied when the surface is created as the first tab is opened. Only takes effect if called before any tab is open - without it, the surface is created at the device's display size.
Fails if the surface size has already been initialized, if a surface already exists, or if the requested size is outside the supported bounds: each side between 240 and 2560 device pixels, total pixel count no more than 2560x1440, and `deviceScaleFactor` between 0.5 and 4.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_initSurface",
"params": [{
"width": number, // device pixels
"height": number, // device pixels
"deviceScaleFactor"?: number // device pixels per CSS pixel, default: 1
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.27.0
#### `browser_surfaceSize`
Returns the size of the surface the browser paints its pages to, or `null` if the browser currently has no surface.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_surfaceSize",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"size": {
"width": number, // device pixels
"height": number, // device pixels
"deviceScaleFactor": number // device pixels per CSS pixel
} | null
},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_newTab`
Opens a new tab in the WebView and returns its ID.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_newTab",
"params": [{ "url": string }],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"id": number
},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_closeTab`
Closes a tab by ID. Set `wholeTree` to `true` to also close all tabs spawned by this tab.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_closeTab",
"params": [{
"id": number,
"wholeTree"?: boolean // default: false
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_openTabs`
Returns a list of currently open tab IDs.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_openTabs",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"tabs": Array
},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_currentUrl`
Returns the current URL of a tab, or `null` if it has not loaded yet.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_currentUrl",
"params": [{ "id"?: number }],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"url": string | null
},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_loadCause`
Returns why the tab last loaded: `"manual"` (opened via `browser_newTab`) or `"auto"` (opened by a tab autonomously), or `null` if not yet loaded.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_loadCause",
"params": [{ "id"?: number }],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"cause": "manual" | "auto" | null
},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_startRefreshLoop`
Starts a periodic refresh loop for a tab. Stop with `browser_stopRefreshLoop` when no longer needed.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_startRefreshLoop",
"params": [{
"id"?: number,
"interval"?: number, // ms, default: 500
"wholeTree"?: boolean // default: false
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_stopRefreshLoop`
Stops the refresh loop for a tab.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_stopRefreshLoop",
"params": [{
"id"?: number,
"wholeTree"?: boolean // default: false
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_useProxy`
Configures the WebView to route traffic through a proxy server.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_useProxy",
"params": [{
"url": string,
"config"?: {
"username"?: string,
"password"?: string,
"fallback"?: boolean // default: false — connect directly if proxy fails
}
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_removeProxy`
Removes the current proxy configuration from the WebView.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_removeProxy",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.25.0+
#### `browser_close`
Closes all open tabs and destroys the WebView, clearing storage, cookies, and proxy settings.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "browser_close",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
### Network
Android: 1.25.0+
#### `network_whitelist`
Verifies `host` via DNS TXT records and, if valid, whitelists its resolved IP addresses so they bypass the rate-limiter in the network extension.
The processor performs the following verification steps:
1. **Forward DNS + TXT** - verifies that `_acu.` carries a TXT record `v=base64(sha256(deployment_source || host))`, then resolves the hostname to one or more IP addresses (A/AAAA).
2. **Reverse DNS + TXT** - for each resolved IP, performs a PTR lookup to obtain the PTR hostname and verifies that `_acu.` carries a TXT record `v=base64(sha256(deployment_source || ptr_hostname))`.
Both steps must pass for an IP to be added to the whitelist. Connections to non-whitelisted hosts are rate-limited.
`deployment_source` is the raw 32-byte Substrate Account ID of the deployment's owner. `host` is the bare hostname (no scheme, port, or path — e.g. `example.com`).
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "network_whitelist",
"params": [{ "host": string }],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
##### Verification hash
Example scripts for computing the TXT record value:
```javascript title="Node.js"
// npm install @polkadot/util-crypto
const { createHash } = require('crypto');
const { decodeAddress } = require('@polkadot/util-crypto');
function verificationHash(source, host) {
// Decode hex Account ID directly, or convert SS58 address to raw bytes
const sourceBytes = source.length === 64
? Buffer.from(source, 'hex')
: Buffer.from(decodeAddress(source));
// SHA-256 over concatenation of source bytes and UTF-8 encoded host
const digest = createHash('sha256')
.update(sourceBytes)
.update(host)
.digest();
// Return the hash as a base64 string
return digest.toString('base64');
}
```
```python title="Python"
# pip install substrate-interface
from substrateinterface.utils.ss58 import ss58_decode
def verification_hash(source: str, host: str) -> str:
# Decode hex Account ID directly, or convert SS58 address to raw bytes
source_bytes = bytes.fromhex(source) if len(source) == 64 else bytes.fromhex(ss58_decode(source))
# SHA-256 over concatenation of source bytes and UTF-8 encoded host
digest = hashlib.sha256(source_bytes + host.encode()).digest()
# Return the hash as a base64 string
return base64.b64encode(digest).decode()
```
---
### Tunnel
Opens a reverse tunnel that forwards inbound TLS connections from a public URL (`https://.:8443`) to a local address inside the deployment. Only one tunnel may be active per deployment.
Before calling `tunnel_start`, the `` must have the required wildcard and verification TXT records — see the [tunnel quick start](https://github.com/Acurast/quic-tunnel/blob/main/QUICKSTART.md) for details.
Android: 1.26.0+
#### `tunnel_start`
Opens the tunnel and returns the public URL it serves.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "tunnel_start",
"params": [{
"serverAddrs": string[], // relay endpoints, e.g. ["relay-2.canary.acurast.com:4433"]
"domainSuffix": string, // DNS suffix you control
"localAddr": string, // local "host:port" to forward to
"primaryKey": {
"algorithm": "Secp256r1", // only P-256 is accepted
"bytes": string // base64 of PKCS#8 DER (no wrap)
},
"acmeStaging"?: boolean, // default false
"acmeEmail"?: string, // Let's Encrypt account contact
"certPem"?: string, // pre-supplied PEM chain; when set, ACME is skipped
"forceH2"?: boolean, // skip QUIC, use HTTP/2 fallback pool (default false)
"poolSize"?: number // H2 pool size when forceH2 is on (default 4)
}],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"url": string, // https://.:8443
"clientId": string, // the deployment's tunnel identifier
"secondaryUrl"?: string,
"secondaryClientId"?: string
},
"id": string
}
```
Persist `primaryKey.bytes` across deployment restarts to keep the same `clientId`. Persist the cert PEM from `tunnel_certPem` and pass it back here as `certPem` to skip ACME on subsequent starts.
---
Android: 1.26.0+
#### `tunnel_stop`
Closes the active tunnel. The cached ACME credentials and certificate are preserved on disk so a subsequent `tunnel_start` with the same identity can reuse them. No-op if no tunnel is running.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "tunnel_stop",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {},
"id": string
}
```
---
Android: 1.26.0+
#### `tunnel_status`
Returns the current tunnel status.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "tunnel_status",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"status": number // 0 = Starting, 1 = Running, 2 = Stopped, 3 = Failed, -1 = no tunnel active
},
"id": string
}
```
---
Android: 1.26.0+
#### `tunnel_certPem`
Returns the PEM-encoded full certificate chain currently issued for the tunnel.
```ts title="Request"
{
"jsonrpc": "2.0",
"method": "tunnel_certPem",
"params": [],
"id": string
}
```
```ts title="Response"
{
"jsonrpc": "2.0",
"result": {
"pem"?: string // omitted if no certificate has been issued yet (or no tunnel is active)
},
"id": string
}
```
---
## Examples
The examples below call `processor_version` to show how to connect to the bridge and make a request in different languages.
### Node.js
```js
const net = require('net');
const client = net.createConnection('\0' + process.env.BRIDGE_SOCKET);
const request = JSON.stringify({
jsonrpc: '2.0',
method: 'processor_version',
params: [],
id: '1',
});
client.write(request + '\n');
client.once('data', (data) => {
const response = JSON.parse(data.toString());
console.log(response);
client.end();
});
```
### Python
```python
request = json.dumps({
'jsonrpc': '2.0',
'method': 'processor_version',
'params': [],
'id': '1'
})
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect('\0' + os.environ['BRIDGE_SOCKET'])
s.sendall((request + '\n').encode())
response = json.loads(s.recv(65536))
print(response)
```
### Rust
```rust
use std::io::{BufRead, BufReader, Write};
use std::os::linux::net::SocketAddrExt;
use std::os::unix::net::{SocketAddr, UnixStream};
fn main() {
let socket = std::env::var("BRIDGE_SOCKET").unwrap();
let addr = SocketAddr::from_abstract_name(socket.as_bytes()).unwrap();
let mut stream = UnixStream::connect_addr(&addr).unwrap();
let request = serde_json::json!({
"jsonrpc": "2.0",
"method": "processor_version",
"params": [],
"id": "1"
}).to_string();
stream.write_all(format!("{request}\n").as_bytes()).unwrap();
let mut response = String::new();
BufReader::new(&stream).read_line(&mut response).unwrap();
let value: serde_json::Value = serde_json::from_str(&response).unwrap();
println!("{}", value);
}
```
---
## Deployment Config (acurast.json)
Every Acurast project has an `acurast.json` file that describes how the deployment should run: which script to execute, which network to target, how many processors, how long, and under which limits. This page is the full field reference.
You can create it interactively with `acurast init` — see the **[CLI docs](/developers/tools/cli)** for the command walkthrough.
## Example
```json
{
"projects": {
"example": {
"projectName": "example",
"fileUrl": "dist/bundle.js",
"network": "mainnet",
"onlyAttestedDevices": true,
"enableDevtools": false,
"assignmentStrategy": {
"type": "Single"
},
"execution": {
"type": "onetime",
"maxExecutionTimeInMs": 10000
},
"maxAllowedStartDelayInMs": 10000,
"usageLimit": {
"maxMemory": 0,
"maxNetworkRequests": 0,
"maxStorage": 0
},
"numberOfReplicas": 64,
"requiredModules": [],
"minProcessorReputation": 0,
"maxCostPerExecution": 100000000000,
"includeEnvironmentVariables": [],
"processorWhitelist": [],
"mutability": "Immutable",
"reuseKeysFrom": null
}
}
}
```
## Fields
| Field | Description |
| --- | --- |
| `projectName` | The name of the project |
| `fileUrl` | Path to the bundled file including all dependencies (e.g., `dist/bundle.js`) |
| `network` | Network for deployment (e.g., `mainnet`, `canary`) |
| `onlyAttestedDevices` | Only allow attested devices to run the app |
| `enableDevtools` | Enable [DevTools](/developers/tools/devtools) for the deployment. Defaults to `false` |
| `startAt` | Start time — either `{ msFromNow: number }` or `{ timestamp: number }` |
| `assignmentStrategy` | `"Single"` (one set of processors) or `"Competing"` (new processors per execution) |
| `execution` | `"onetime"` or `"interval"` with `intervalInMs`, `numberOfExecutions`, and `maxExecutionTimeInMs` |
| `maxAllowedStartDelayInMs` | Maximum allowed start delay in milliseconds |
| `usageLimit` | Limits for `maxMemory`, `maxNetworkRequests`, and `maxStorage` (in bytes) |
| `numberOfReplicas` | How many processors run the deployment in parallel |
| `requiredModules` | Modules the processor must support. Supported values: `"DataEncryption"`, `"LLM"`, `"Shell"`. Defaults to `[]`. When `runtime` is `"Shell"`, `"Shell"` is auto-injected |
| `runtime` | Runtime used to execute the deployment. `"NodeJSWithBundle"` (default), `"NodeJS"`, or `"Shell"`. See [Shell Runtime](/developers/tools/cli#shell-runtime) |
| `image` | Linux distro image used by the Shell runtime. Required when `runtime` is `"Shell"`. Object: `{ url, sha256 }` (HTTPS `.tar.xz` URL + SHA256). Ignored otherwise |
| `entrypoint` | Script or binary the processor runs after extracting the Shell image (e.g. `acurast.sh`) |
| `restartPolicy` | `"no"` (default — run once, no retry) or `"onFailure"` (retry up to 3 times on failure; only the third failure is reported) |
| `minProcessorReputation` | Minimum required processor reputation |
| `maxCostPerExecution` | Maximum cost per execution in the smallest denomination of ACU |
| `includeEnvironmentVariables` | Environment variables from `.env` to pass to the deployment. See [Environment Variables](/developers/build/environment-variables) |
| `processorWhitelist` | Whitelist of processor addresses |
| `minProcessorVersions` | Minimum processor versions (`android`, `ios`) |
| `mutability` | `"Immutable"` (default) or `"Mutable"` — controls whether the deployment can be modified after creation |
| `reuseKeysFrom` | Reuse keys from a previous mutable deployment. Format: `["Acurast", "address", deploymentId]` |
## `.env`
Secrets and environment variables for the CLI/SDK (not to be confused with deployment-runtime env vars — see [Environment Variables](/developers/build/environment-variables)).
```text
ACURAST_MNEMONIC=abandon abandon about ...
# ACURAST_IPFS_URL=https://api.pinata.cloud
# ACURAST_IPFS_API_KEY=eyJhb...
# ACURAST_RPC=wss://...
```
| Variable | Required | Description |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | Yes | Mnemonic for the deployer account. Must have ACU (or cACU on canary). Claim cACU on the [faucet](https://faucet.acurast.com) |
| `ACURAST_IPFS_URL` | No | IPFS gateway URL (e.g., `https://api.pinata.cloud`) |
| `ACURAST_IPFS_API_KEY` | No | API key for the IPFS gateway. [Register here](https://pinata.cloud/) |
| `ACURAST_RPC` | No | Custom RPC URL |
## See also
- [CLI reference](/developers/tools/cli) — commands that consume this config
- [Node.js Runtime Environment](/developers/build/nodejs-runtime-environment) — what's available to your Node.js script when it runs
- [Cargo Runtime Environment](/developers/build/cargo-runtime-environment) — what's available to your Cargo deployment when it runs
- [Environment Variables](/developers/build/environment-variables) — passing encrypted secrets to your deployment
---
## Environment Variables
You can pass encrypted environment variables to your Acurast deployments. They are encrypted at deployment time and only decrypted when the code runs on a processor. Useful for API keys and other secrets.
## Setup
**1. Add variables to your local `.env` file:**
```text
API_KEY=your-api-key
```
**2. Reference them in `acurast.json`:**
```json
{
"projects": {
"my-project": {
"includeEnvironmentVariables": ["API_KEY"]
}
}
}
```
See the [Deployment Config reference](/developers/build/deployment-config) for all config fields.
**3. Access them in your deployment code:**
On `acurast deploy`, the listed variables are encrypted and attached to the deployment automatically. How you access them in your code depends on the runtime environment:
Node.js Runtime Environment
```typescript
const API_KEY = _STD_.env.API_KEY;
```
Cargo Runtime Environment
In the Cargo runtime, environment variables are available as standard system environment variables on the deployment's distro image. For example, in Node.js:
```typescript
const API_KEY = process.env.API_KEY;
```
## Rotating variables between executions
For interval-based deployments with multiple executions, you can update environment variables between runs. Edit `.env` and then:
```bash
acurast deployments --update-env-vars
```
This is useful for rotating API keys on a schedule without redeploying the script.
## Programmatic access (SDK)
When using the [SDK](/developers/tools/sdk), the same workflow is available via `setEnvVars` and `JobEnvironmentService` from `@acurast/sdk/chain`.
## See also
- [Node.js Runtime Environment](/developers/build/nodejs-runtime-environment) - the full `_STD_` API available to Node.js scripts
- [Cargo Runtime Environment](/developers/build/cargo-runtime-environment) - the RPC API available to Cargo deployments
- [CLI reference](/developers/tools/cli)
---
## How to run an LLM on Acurast
## Introduction
This tutorial walks you through deploying and running an LLM on Acurast.
Acurast includes a module for running LLMs. Most models from [Hugging Face](https://huggingface.co/) in the `GGUF` format are supported.
:::tip
If you prefer to jump right in, you can take a look at the example project:
- [LLMs on Acurast](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-llm)
You can either clone the repository, or set up a blank Acurast starter project by running `npx @acurast/cli new `.
:::
## Prerequisites
- Basic knowledge of Node.js and the Command Line
## Setting up the Project
### Project Structure
The structure of a project looks exactly like a normal Node.js project:
```
├── dist
│ └── bundle.js
├── LICENSE
├── README.md
├── acurast.json
├── package-lock.json
├── package.json
├── src
│ └── index.ts
├── .env
├── tsconfig.json
└── webpack.config.js
```
There is only one file that is specific to Acurast: `acurast.json`. This file configures the deployment and is covered later in the tutorial.
### Writing the code
First, let's start by creating a simple node.js project. You can find the code of the example, including all the build steps and configurations, on [GitHub](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-llm)
The app will host a local LLM server and make it available over HTTP.
```typescript
MODEL_URL,
MODEL_NAME,
STORAGE_DIR,
LOCALTUNNEL_HOST,
} from "./constants";
declare let _STD_: any;
const MODEL_FILE = path.resolve(STORAGE_DIR, MODEL_NAME);
async function downloadModel(url: string, dst: string) {
console.log("Downloading model", MODEL_NAME);
const res = await fetch(url);
if (!res.body) {
throw new Error("No response body");
}
console.log("Writing model to file:", dst);
const writer = createWriteStream(dst);
await finished(Readable.fromWeb(res.body as any).pipe(writer));
}
async function main() {
if (!existsSync(MODEL_FILE)) {
await downloadModel(MODEL_URL, MODEL_FILE);
} else {
console.log("Using already downloaded model:", MODEL_FILE);
}
console.log("model downloaded");
_STD_.llama.server.start(
["--model", MODEL_FILE, "--ctx-size", "2048", "--threads", "8"],
() => {
// onCompletion
console.log("Llama server closed.");
},
(error: any) => {
// onError
console.log("Llama server error:", error);
throw error;
}
);
const tunnel = await localtunnel({
port: 8080,
host: LOCALTUNNEL_HOST,
subdomain: _STD_.device.getAddress().toLowerCase(),
});
console.log(tunnel.url);
}
main();
```
This code first downloads a model from Hugging Face, then starts the integrated LLM server and loads it. Finally, it uses localtunnel to make the server publicly available.
:::tip
The API is compatible with the [OpenAI-like API endpoints](https://lmstudio.ai/docs/basics/server#openai-like-api-endpoints)
:::
Set the `LOCALTUNNEL_SUBDOMAIN` variable to specify where the server will be available. If set to `llm`, the URL will be `https://llm.acu.run`.
:::note
This localtunnel server is not secure and should not be used in production. Work is underway to make this secure by default, but if a secure way to host your project is needed now, please reach out via the community channels.
:::
### Building the project
To deploy a project to the Acurast Cloud, it needs to be bundled into a single js file. This example uses webpack. You can find the configuration in the example project on [GitHub](https://github.com/Acurast/acurast-example-apps/blob/main/apps/app-llm/)
Running `npm run bundle` will then output a single js file which includes all necessary dependencies.
The file is located in `dist/bundle.js`. It includes your code, as well as all the dependencies in a single file.
This is the file that will be deployed to the Acurast Cloud.
## Setting up the Acurast CLI
Now that the app is ready, the Acurast CLI needs to be set up. The CLI is a tool that allows you to deploy and manage your applications on the Acurast Cloud.
### Installation
Let's install the Acurast CLI globally using npm:
```bash
npm install -g @acurast/cli
```
To verify that the installation worked, you can run `acurast` in the terminal and it will show you the help page:
```text
tutorial % acurast
_ _ ____ _ ___
/ \ ___ _ _ _ __ __ _ ___| |_ / ___| | |_ _|
/ _ \ / __| | | | '__/ _` / __| __| | | | | | |
/ ___ \ (__| |_| | | | (_| \__ \ |_ | |___| |___ | |
/_/ \_\___|\__,_|_| \__,_|___/\__| \____|_____|___|
Usage: acurast [options] [command]
A cli to interact with the Acurast Network.
Options:
-v, --version output the version number
-h, --help display help for command
Commands:
deploy [options] [project] Deploy the current project to the Acurast platform.
init Create an acurast.json and .env file
live [options] [project] Run the code in a live code environment on a remote processor
open Open Acurast websites in your browser
help [command] display help for command
```
### Adding Acurast Config to the Project
The next step is to add the Acurast Config to the project. To do that, run the following command:
```bash
acurast init
```
This will start an interactive guide, which will create an `.env` file.
If you checked out the sample project, the `acurast.json` already exists, so this step will be skipped. You can open the `acurast.json` file and change the configuration there. In the [CLI Docs](https://github.com/Acurast/acurast-cli?tab=readme-ov-file#configuration-details) you will find more information about the possible configurations.
### Getting ready for Deployment
To deploy the application, one more step is needed: funding the account.
:::info Mainnet vs Canary
The faucet only works on **Canary** (cACU). On **Mainnet**, ACU must be acquired via an exchange or bridge — see **[How to Get ACU](/token-holders/how-to-get-acu)**.
:::
> [!TIP]
> You can import the mnemonic that was generated and stored in the .env file and import it in Talisman (Browser Extension) to access the same account in the [Web Console](https://hub.acurast.com/).
Let's get some tokens on your new account. You can run the `acurast deploy` command, which will check your balance, and displays the link to the Faucet page.
```text
tutorial % acurast deploy --dry-run
Deploying project "app-llm"
Your balance is 0. Visit https://faucet.acurast.com?address=5GNimXAQhayQq8m8SxJt3xQmG2L3pGzeTkHopx9iPnrS6uHP to get some tokens.
```
Visit the link displayed in the CLI and follow the instructions to get some tokens. They should be available in a few seconds.
That's it! You're now ready to deploy your app.
## Deploying the Application
To deploy your application, run `acurast deploy`:
```text
tutorial % acurast deploy
Deploying project "tutorial"
The CLI will use the following address: 5GNimXAQhayQq8m8SxJt3xQmG2L3pGzeTkHopx9iPnrS6uHP
The deployment will be scheduled to start in 5 minutes 0 seconds.
There will be 1 executions with a cost of 0.001 cACU each.
❯ Deploying project (first execution scheduled in 246s)
✔ Submitted to Acurast (ipfs://...)
✔ Deployment registered (DeploymentID: ...)
⠇ Waiting for deployment to be matched with processors
◼ Waiting for processor acknowledgements
```
Congratulations, your deployment is now being registered in the network and executed soon! Check the CLI for more information about the deployment process.
## Verifying the Deployment
If you followed this tutorial, then your app will be available at `https://.acu.run`. ("\" is the value you set for `LOCALTUNNEL_SUBDOMAIN` in the code).
Success! You've successfully deployed your first application on Acurast!
## Conclusion
Congratulations! You've successfully deployed your first application on Acurast! For more advanced features and detailed documentation, refer to [Acurast CLI Documentation](https://github.com/Acurast/acurast-cli/blob/main/README.md). Also make sure to join the Telegram or Discord to be part of the community!
## More Examples
For more inspiration, check out the [Acurast Examples](https://github.com/Acurast/acurast-example-apps) with examples showing various features:
- [app-env-vars](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-env-vars)
- [external-dependencies](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-external-dependencies)
- [fetch from API](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-fetch)
- [heic to png](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-heic-to-png)
- [puppeteer](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-puppeteer)
- [telegram-bot](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-telegram-bot)
- [wasm](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-wasm)
- [webserver](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-webserver)
---
## Node.js Runtime Environment
The deployment app running in the Node.js runtime environment on Processors have access to the following set of APIs.
## **Top level functions**
```javascript
/**
* Prints the given message to the console.
* @param {string} message the message to print.
*/
function print(message);
/**
* Performs an HTTP GET request.
* @param {string} url the url to connect to.
* @param {Record} headers the request's headers, for example: { 'Accept': 'application/json' }.
* @param {HttpSuccess} success the success callback function.
* @param {HttpError} error the error callback function.
*/
function httpGET(url, headers, success, error);
/**
* Performs an HTTP GET request.
* @param {string} url the url to connect to.
* @param {string} body a string representing the request's body.
* @param {Record} headers the request's headers, for example: { 'Content-Type': 'application/json' }.
* @param {HttpSuccess} success the success callback function.
* @param {HttpError} error the error callback function.
*/
function httpPOST(url, body, headers, success, error);
/**
* @callback HttpSuccess
* @param {string} payload the http request response payload as text.
* @param {string} certificate a hex string representing the server certificate.
*/
type HttpSuccess = (payload, certificate) => void;
/**
* @callback HttpError
* @param {string} message an error message.
*/
type HttpError = (message) => void;
/**
* Reads value from the environment.
* @param {string} key The key used to get the value from the environment.
* @return {string} The string value for the given key or undefined.
*/
function environment(key);
```
## **The \_STD\_ object**
At the top level, a `_STD_` object is defined. This object exposes additional functionalities.
### **Random**
```javascript
/**
* Generates random bytes.
* @return {string} Hex string representing random bytes.
*/
_STD_.random.generateSecureRandomHex();
```
### **Environment Variables**
```javascript
/**
* Environment object populated with the environment variables defined during deployment creation.
* For example, to access a variable defined with the "MY_KEY" key do: _STD_.env["MY_KEY"].
*/
_STD_.env;
```
### **App Info**
```javascript
/**
* The processor app version as a string.
*
* Example: "1.9.2-canary"
*/
_STD_.app_info.version;
```
### **Deployment Info**
```javascript
/**
* @return {DeploymentId} Object representing a deployment id.
*
* Example:
* {
* origin: {
* kind: "Acurast",
* source: "2273f64ccf6e9dc13aedf111ca19da030909374f18c6a958b8e5c64927dc7b4f"
* },
* id: "3510"
* }
*/
_STD_.job.getId();
type DeploymentId = { origin: { kind: string, source: string }, id: string };
/**
* @return {number} The slot number of this deployment.
*/
_STD_.job.getSlot();
/**
* @return {PublicKeys} Object containing the deployment specific public keys.
*
* Example:
* {
* p256: "03aa8fa2bfe5a5d6789637c3b82b322b617f8c19e29a4b7d3eede17a2583312891",
* secp256k1: "02fcf1a928bab608989a0218831efd585d1e771669756e1033c60cff4bef6f28e5",
* ed25519: "7ce9f9b96a0f898ad109a594ab2c30a1682e7e6425910427c9390fdf16b11dd6"
* }
*/
_STD_.job.getPublicKeys();
type PublicKeys = { p256: string, secp256k1: string, ed25519: string };
```
### **Device Info**
```javascript
/**
* Get the main account public key.
*
* @since 1.9.2 (version code 58)
*
* @return {string} Hex string representing the main account public key.
*/
_STD_.device.getPublicKey();
/**
* Get the main account address.
*
* @since 1.9.2 (version code 58)
*
* @return {string} String representing the main account address.
*/
_STD_.device.getAddress();
```
### **Signers**
Each deployment gets its own set of keys on every processor it runs on, one per curve. The public keys are returned by `_STD_.job.getPublicKeys()` and are also recorded on-chain in the deployment's match record (`assignment.pubKeys`). The signing functions below use the matching private key, which never leaves the processor.
:::info Key naming
The same P-256 key shows up under three names: `getPublicKeys().p256`, `_STD_.signers.secp256r1`, and the on-chain `SECP256r1` entry. P-256, secp256r1 and prime256v1 are all the same curve.
:::
:::warning ECDSA signers sign a digest, not a message
`secp256r1.sign()` and `secp256k1.sign()` do **not** hash their input. They perform a raw ECDSA signature over exactly the bytes you pass, so you must hash the message yourself (usually SHA-256) and pass the 32-byte digest. If you pass the message itself, a standard `verify('sha256', message, signature)` on the server will fail. `ed25519.sign()` is different: Ed25519 hashes internally, so pass the full message.
The ECDSA signature is returned as raw `r || s` (64 bytes, hex), **not** DER. `secp256k1` signatures are low-`s` normalized and carry no recovery byte. Convert to DER if your verifier needs it (Node: `crypto.verify(..., { key, dsaEncoding: 'ieee-p1363' }, ...)` accepts `r || s` directly).
See [Signing requests from a deployment](/developers/examples/signing-requests) for a full hash → sign → verify walkthrough.
:::
```javascript
/**
* Signs the given digest with the secp256r1 (P-256) key generated for the current deployment.
* The matching public key is `_STD_.job.getPublicKeys().p256`.
*
* @since 1.9.2 (version code 58)
*
* @param {string} payload Hex string of the bytes to sign. This is NOT hashed: pass a 32-byte digest (e.g. sha256 of your message).
* @return {string} Hex string of the raw `r || s` signature (64 bytes, not DER).
*/
_STD_.signers.secp256r1.sign(payload);
/**
* Encrypts the given payload with the secp256r1 key generated for the current deployment.
*
* @since 1.9.2 (version code 58)
*
* @param {string} publicKey Hex string representing the receiver's public key.
* @param {string} salt Hex string representing the salt used for encryption.
* @param {string} payload Hex string representing the bytes to encrypt.
* @return {string} Hex string representing the encrypted payload.
*/
_STD_.signers.secp256r1.encrypt(publicKey, salt, payload);
/**
* Decrypts the given payload with the secp256r1 key generated for the current deployment.
*
* @since 1.9.2 (version code 58)
*
* @param {string} publicKey Hex string representing the sender's public key.
* @param {string} salt Hex string representing the salt used for encryption.
* @param {string} payload Hex string representing the bytes to decrypt.
* @return {string} Hex string representing the decrypted payload.
*/
_STD_.signers.secp256r1.decrypt(publicKey, salt, payload);
/**
* Signs the given digest with the secp256k1 key generated for the current deployment.
* The matching public key is `_STD_.job.getPublicKeys().secp256k1`.
*
* @since 1.9.2 (version code 58)
*
* @param {string} payload Hex string of the bytes to sign. This is NOT hashed: pass a 32-byte digest.
* @return {string} Hex string of the raw `r || s` signature (64 bytes, low-s, no recovery byte, not DER).
*/
_STD_.signers.secp256k1.sign(payload);
/**
* Encrypts the given payload with the secp256k1 key generated for the current deployment.
*
* @since 1.9.2 (version code 58)
*
* @param {string} publicKey Hex string representing the receiver's public key.
* @param {string} salt Hex string representing the salt used for encryption.
* @param {string} payload Hex string representing the bytes to encrypt.
* @return {string} Hex string representing the encrypted payload.
*/
_STD_.signers.secp256k1.encrypt(publicKey, salt, payload);
/**
* Decrypts the given payload with the secp256k1 key generated for the current deployment.
*
* @since 1.9.2 (version code 58)
*
* @param {string} publicKey Hex string representing the sender's public key.
* @param {string} salt Hex string representing the salt used for encryption.
* @param {string} payload Hex string representing the bytes to decrypt.
* @return {string} Hex string representing the decrypted payload.
*/
_STD_.signers.secp256k1.decrypt(publicKey, salt, payload);
/**
* Signs the given message with the ed25519 key generated for the current deployment.
* The matching public key is `_STD_.job.getPublicKeys().ed25519`.
*
* @since 1.9.2 (version code 58)
*
* @param {string} payload Hex string of the message to sign. Unlike the ECDSA signers, Ed25519 hashes internally: pass the full message, not a digest.
* @return {string} Hex string representing the 64-byte signature.
*/
_STD_.signers.ed25519.sign(payload);
```
A SHA-256 helper is available at `_STD_.chains.bitcoin.signer.sha256(hex)` (see [Bitcoin message signing](#bitcoin-message-signing)). Since deployments run in Node.js you can also use `require('crypto').createHash('sha256')`.
### **Websocket (deprecated)** {#websocket}
:::warning Deprecated
The Websocket API is deprecated. Use the [P2P API](#p2p) instead, which covers the same
processor-to-processor messaging. Existing deployments continue to work, but new deployments
should not use `_STD_.ws`.
:::
```javascript
/**
* @deprecated Use `_STD_.p2p` instead.
*
* @param {string | string[]} url to the acurast websocket service.
* @param {WsSuccess} success the success callback.
* @param {WsError} error the error callback.
*/
_STD_.ws.open(url, success, error);
/**
* @deprecated Use `_STD_.p2p` instead.
*
* @param {WsSuccess} success the success callback.
* @param {WsError} error the error callback.
*/
_STD_.ws.close(success, error);
/**
* @deprecated Use `_STD_.p2p` instead.
*
* @param {WsHandler} handler the handler called on every incoming message.
*/
_STD_.ws.registerPayloadHandler(handler);
/**
* @deprecated Use `_STD_.p2p` instead.
*
* @param {string} recipient the public key in hex format of the recipient.
* @param {string} payload the payload to send as a hex string.
* @param {WsSuccess} success the success callback.
* @param {WsError} error the error callback.
*/
_STD_.ws.send(recipient, payload, success, error);
/**
* @callback WsSuccess
*/
type WsSuccess = () => void;
/**
* @callback WsError
* @param {string} message an error message.
*/
type WsError = (message) => void;
/**
* @callback WsHandler
* @param {WsPayload} payload the payload message.
*/
type WsHandler = (payload) => void;
type WsPayload = { sender: string, recipient: string, payload: string };
```
### **P2P**
```javascript
/**
* @param {P2PConfig} config the node configuration.
* @param {P2PSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.start(config, success, error);
/**
* @param {P2PSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.close(success, error);
/**
* @param {P2PMessageListener} listener the listener called on each incoming message.
*/
_STD_.p2p.onMessage(listener);
/**
* @param {string} receiver the address or peer ID of the peer who should receive the message.
* @param {string} protocol the ID of the message protocol that should be used to transmit the message.
* @param {string} bytes the payload to send as a hex string.
* @param {P2PSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.request(receiver, protocol, bytes, success, error);
/**
* @param {P2PMessage} request the request to which this message responds.
* @param {string} bytes the payload to send as a hex string.
* @param {P2PSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.respond(request, bytes, success, error);
/**
* @param {string} peer the address or peer ID of the target peer to establish a connection with.
* @param {P2PConnectOptions|undefined} options an optional configuration of this call.
* @param {P2PSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.connect(peer, options, success, error);
/**
* @param {string} peer the address or peer ID of the target peer whose connection should be terminated.
* @param {P2PSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.disconnect(peer, success, error);
/**
* @param {string} peer the address or peer ID of the target peer to which the stream will be opened.
* @param {string} protocol the protocol to be used for the stream.
* @param {P2PStreamSuccess} success the success callback.
* @param {P2PError} error the error callback.
*/
_STD_.p2p.openOutgoingStream(peer, protocol, success, error);
/**
* @param {P2PStreamListener} listener the listener called on each incoming stream.
*/
_STD_.p2p.onIncomingStream(listener);
/**
* @param {P2PConnectedRelayListener} listener the listener called whenever a relay is connected.
*/
_STD_.p2p.onRelayConnected(listener);
/**
* @param {string} publicKey the public key from which the peer ID should be generated.
* @return {string} the peer ID.
*/
_STD_.p2p.peerIdFromPublicKey(publicKey): string;
/**
* @property {string[]} messageProtcols message protocols the node will support and use to send and receive messages.
* @property {string[]} relays a list of public nodes that will serve as a proxy helping establish connections with nodes behind NATs and firewalls.
* @property {number|undefined} idleConnectionTimeout time in milliseconds after which idle connections will be closed, defaults to 15s if not provided.
*/
type P2PConfig = {
messageProtocols: string[]
relays: string[]
idleConnectionTimeout?: number
};
/**
* @callback P2PSuccess
*/
type P2PSuccess = () => void;
/**
* @callback P2PStreamSuccess
* @param {P2PStream} stream
*/
type P2PStreamSuccess = (stream) => void;
/**
* @callback P2PError
* @param {string} message an error message.
*/
type P2PError = (message) => void;
/**
* @callback P2PMessageListener
* @param {P2PMessage} message
*/
type P2PMessageListener = (payload) => void;
/**
* @property {number|string|undefined} timeout an optional duration in milliseconds for which the client will attempt to establish a connection with the peer. If the connection is being established through a relay, the client will wait for a direct connection within the timeout period. If unsuccessful, it will fallback to the relayed connection, if available.
*/
type P2PConnectOptions = {
timeout?: number | string
}
/**
* @callback P2PStreamListener
* @param {P2PStream} stream
*/
type P2PStreamListener = (stream) => void;
/**
* @callback P2PConnectedRelayListener
* @param {string} address the address of the connected relay.
*/
type P2PConnectedRelayListener = (address) => void;
/**
* @property {P2PMessageType} type the type of the message.
* @property {string} id internal id,
* @property {P2Peer} sender the sender of the message.
* @property {string} protocol the message protocol that was used to transmit this message.
* @property {string} bytes the payload represented as a hex string.
*/
type P2PMessage = {
type: P2PMessageType
id: string
sender: P2PPeer
protocol: string
bytes: string
};
type P2PMessageType = 'request' | 'response';
type P2PPeer = { type: P2PPeerType, value: string };
type P2PPeerType = 'address' | 'peerId';
/**
* @property {string} protocol
* @property {P2PPeer} peer
* @function read reads n bytes from the stream.
* @function write writes bytes to the stream.
* @function close closes the stream.
*/
type P2PStream = {
protocol: string
peer: P2PPeer
read: P2PStreamRead
write: P2PStreamWrite
close: P2PStreamClose
};
/**
* @function P2PStreamRead
* @param {number} n the number of bytes to read from the stream.
* @return {Promise} a promise that resolves with bytes read.
*/
type P2PStreamRead = (n) => Promise;
/**
* @function P2PStreamWrite
* @param {Uint8Array | string} bytes the data to be written to the stream, provided as a `Uint8Array` or hex string.
*/
type P2PStreamWrite = (bytes) => Promise;
/**
* @function P2PStreamClose
*/
type P2PStreamClose = () => Promise;
```
### **Tunnel**
Exposes the deployment's reverse tunnel to job scripts. The tunnel forwards inbound TLS connections (terminated at `https://.:8443`) to a local address inside the deployment. See [Tunnel quick start](/developers/getting-started/quickstart-tunnel) for the DNS records that must be in place on `` before calling `start`.
```javascript
/**
* Opens the deployment's reverse tunnel. Only one tunnel may be active per
* deployment; calling `start` again before `stop` results in an error.
*
* @since Android 1.26.0
*
* @param {TunnelSpec} spec the tunnel configuration.
* @param {TunnelStartSuccess} success the success callback, invoked with the tunnel info.
* @param {TunnelError} error the error callback.
*/
_STD_.tunnel.start(spec, success, error);
/**
* Closes the active tunnel. The cached ACME credentials and certificate are
* preserved on disk so a subsequent `start` with the same identity reuses them.
*
* @since Android 1.26.0
*
* @param {TunnelSuccess} success the success callback.
* @param {TunnelError} error the error callback.
*/
_STD_.tunnel.stop(success, error);
/**
* Returns the tunnel status ordinal:
* 0 = Starting, 1 = Running, 2 = Stopped, 3 = Failed, -1 = no tunnel active.
*
* @since Android 1.26.0
*
* @param {TunnelStatusSuccess} success the success callback.
* @param {TunnelError} error the error callback.
*/
_STD_.tunnel.status(success, error);
/**
* Returns the PEM-encoded full certificate chain currently issued for the
* tunnel, or `null` if no certificate has been issued yet (or no tunnel is
* active). Persist this value and pass it back via `spec.certPem` on the
* next `start` to skip the ACME flow.
*
* @since Android 1.26.0
*
* @param {TunnelCertPemSuccess} success the success callback.
* @param {TunnelError} error the error callback.
*/
_STD_.tunnel.certPem(success, error);
/**
* @typedef TunnelSpec
* @property {string[]} serverAddrs one or more `host:port` relay endpoints.
* @property {string} domainSuffix DNS suffix you control; must satisfy the DNS prerequisites.
* @property {string} localAddr local `host:port` to forward decrypted traffic to.
* @property {TunnelPrimaryKey} primaryKey identity key material for the tunnel.
* @property {boolean} [acmeStaging=false] use the Let's Encrypt staging environment.
* @property {string} [acmeEmail] account contact email for Let's Encrypt.
* @property {string} [certPem] pre-supplied full-chain PEM; when set, ACME is skipped.
* @property {boolean} [forceH2=false] skip QUIC and use the HTTP/2 fallback pool.
* @property {number} [poolSize=4] H2 connection pool size when `forceH2` is on.
*/
type TunnelSpec = {
serverAddrs: string[],
domainSuffix: string,
localAddr: string,
primaryKey: TunnelPrimaryKey,
acmeStaging?: boolean,
acmeEmail?: string,
certPem?: string,
forceH2?: boolean,
poolSize?: number,
};
/**
* @typedef TunnelPrimaryKey
* @property {'Secp256r1'} algorithm the curve used. Only P-256 is accepted;
* the relay rejects other algorithms at start time.
* @property {string} bytes base64-encoded PKCS#8 DER private key (no wrap).
*/
type TunnelPrimaryKey = { algorithm: 'Secp256r1', bytes: string };
/**
* @typedef TunnelInfo
* @property {string} url public URL of the form `https://.:8443`.
* @property {string} clientId the deployment's tunnel identifier.
* @property {string} [secondaryUrl] optional secondary tunnel URL.
* @property {string} [secondaryClientId] optional secondary tunnel identifier.
*/
type TunnelInfo = {
url: string,
clientId: string,
secondaryUrl?: string,
secondaryClientId?: string,
};
/**
* @callback TunnelSuccess
*/
type TunnelSuccess = () => void;
/**
* @callback TunnelStartSuccess
* @param {TunnelInfo} info the started tunnel's connection info.
*/
type TunnelStartSuccess = (info) => void;
/**
* @callback TunnelStatusSuccess
* @param {number} status the status ordinal (see above).
*/
type TunnelStatusSuccess = (status) => void;
/**
* @callback TunnelCertPemSuccess
* @param {string | null} pem PEM-encoded cert chain, or `null` if none issued yet.
*/
type TunnelCertPemSuccess = (pem) => void;
/**
* @callback TunnelError
* @param {string} message an error message.
*/
type TunnelError = (message) => void;
```
### **WebView**
```javascript
/**
* @since 1.23.0 (Android)
*
* Opens a new tab in the WebView.
* @param {string} url the URL to open.
* @param {WebViewNewTabSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback.
*/
_STD_.webview.newTab(url, onSuccess, onError);
/**
* @since 1.9.2 (Android)
*
* Closes all open WebView tabs, clears storage, cookies and eventual proxy settings.
* @param {WebViewVoidSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback.
*/
_STD_.webview.close(onSuccess, onError);
/**
* @since 1.23.0 (Android)
*
* Gets an array of all currently open tabs.
* @param {WebViewGetTabsSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback.
*/
_STD_.webview.getOpenTabs(onSuccess, onError);
/**
* @since 1.23.0 (Android)
*
* Configures the WebView to use a proxy server.
* @param {string} url the proxy URL.
* @param {WebViewProxyConfig|undefined} config additional proxy configuration, optional.
* @param {WebViewVoidSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback.
*/
_STD_.webview.useProxy(url, config, onSuccess, onError);
/**
* @since 1.23.0 (Android)
*
* Resets the proxy configuration.
* @param {WebViewVoidSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback.
*/
_STD_.webview.removeProxy(onSuccess, onError);
/**
* @since 1.27.0 (Android)
*
* Sets the size of the surface the browser paints its pages to. Only takes effect if called
* before any tab is open - the surface is created at this size when the first tab opens.
* Without this call the surface is created at the device's display size.
* @param {WebViewSurfaceSizeRequest} size the requested surface size.
* @param {WebViewVoidSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback. Fails if the surface size has already been
* initialized, if a surface already exists, or if `size` is outside the supported bounds: each
* side between 240 and 2560 device pixels, total pixel count no more than 2560x1440, and
* `deviceScaleFactor` between 0.5 and 4.
*/
_STD_.webview.initSurface(size, onSuccess, onError);
/**
* @since 1.27.0 (Android)
*
* Returns the size of the surface the browser paints its pages to.
* @param {WebViewGetSurfaceSizeSuccess} onSuccess the success callback.
* @param {WebViewError} onError the error callback.
*/
_STD_.webview.getSurfaceSize(onSuccess, onError);
/**
* @since 1.9.2 (Android)
*
* Returns the debug URL of the WebView.
* @return {string} the debug URL.
*/
_STD_.webview.getDebugUrl(): string;
/**
* @since 1.23.0 (Android)
*
* @callback WebViewCloseSuccess
* @param {WebviewTab} tab the opened tab.
*/
type WebViewNewTabSuccess = (tab) => void;
/**
* @since 1.23.0 (Android)
*
* @callback WebViewGetTabsSuccess
* @param {WebViewTab[]} tabs the currently opened tabs.
*/
type WebViewGetTabsSuccess = (tabs) => void;
/**
* @since 1.9.2 (Android)
*
* @callback WebViewCloseSuccess
*/
type WebViewVoidSuccess = () => void;
/**
* @since 1.9.2 (Android)
*
* @callback WebViewCloseSuccess
* @param {string} error the error message.
*/
type WebViewError = (error) => void;
/**
* @since 1.23.0 (Android)
*
* @property {string} id the unique identifier of the WebView tab.
* @function getUrl returns the current URL of the WebView tab.
* @function getTrigger returns the trigger mode of the WebView tab.
* @function close closes the WebView tab.
* @function startRefreshLoop starts the refresh loop for the WebView tab.
* @function stopRefreshLoop stops the ongoing refresh loop for the WebView tab.
*/
type WebViewTab = {
id: string
getUrl: WebViewTabGetUrl;
getTrigger: WebViewTabGetTrigger;
close: WebViewTabClose;
startRefreshLoop: WebViewStartRefreshLoop;
stopRefreshLoop: WebViewStopRefreshLoop;
};
/**
* @since 1.23.0 (Android)
*
* @function WebViewTabGetUrl
* Returns the current URL of the WebView tab.
*/
type WebViewTabGetUrl = () => Promise;
/**
* @since 1.23.0 (Android)
*
* @function WebViewTabGetTrigger
* Returns the trigger mode of the WebView tab: 'manual' if opened via `webview.newTab` call, or 'auto' if opened automatically by one of the tabs.
*/
type WebViewTabGetTrigger = () => Promise<'manual' | 'auto'>;
/**
* @since 1.23.0 (Android)
*
* @function WebViewTabClose
* Closes the WebView tab.
* @param {WebViewCloseOptions|undefined} options
*/
type WebViewTabClose = (options) => Promise;
/**
* @since 1.23.0 (Android)
*
* @property {boolean|undefined} wholeTree whether to close the whole tree of tabs spawned by this tab or only the current one. If not provided, the default is false.
*/
type WebViewCloseOptions = {
wholeTree?: boolean;
}
/**
* @since 1.23.0 (Android)
*
* @function WebViewStartRefreshLoop
* Starts the refresh loop for the WebView tab.
*
* This action may increase resource usage, so it should be used sparingly and
* only if absolutely necessary, for example in preparation for taking a screenshot.
*
* When no longer needed, the refresh loop should be closed with `stopRefreshLoop`.
* @param {WebViewStartRefreshLoopOptions|undefined} options
*/
type WebViewStartRefreshLoop = (options) => void;
/**
* @since 1.23.0 (Android)
*
* @property {number|undefined} interval the interval of consecutive refreshes in milliseconds. If not provided, the default interval of 500 milliseconds is used.
* @property {boolean|undefined} wholeTree whether to refresh the whole tree of tabs spawned by this tab or only the current one. If not provided, the default is false.
*/
type WebViewStartRefreshLoopOptions = {
interval?: number;
wholeTree?: boolean;
}
/**
* @since 1.23.0 (Android)
*
* @function WebViewStopRefreshLoop
* Stops the ongoing refresh loop for the WebView tab.
* @param {WebViewStopRefreshLoopOptions|undefined} options
*/
type WebViewStopRefreshLoop = (options) => void;
/**
* @since 1.23.0 (Android)
*
* @property {boolean|undefined} wholeTree whether to stop ongoing refresh loops for the whole tree of tabs spawned by this tab or only the current one. If not provided, the default is false.
*/
type WebViewStopRefreshLoopOptions = {
wholeTree?: boolean;
}
/**
* @since 1.23.0 (Android)
*
* @property {string|undefined} username Proxy server username.
* @property {string|undefined} password Proxy server password.
* @property {boolean|undefined} fallback Whether to connect directly instead of using a proxy server in case of a failure. Defaults to false.
*/
type WebViewProxyConfig = {
username?: string;
password?: string;
fallback?: boolean;
}
/**
* @since 1.27.0 (Android)
*
* @callback WebViewGetSurfaceSizeSuccess
* @param {WebViewSurfaceSize | null} size the surface size, or `null` if the browser currently has no surface.
*/
type WebViewGetSurfaceSizeSuccess = (size) => void;
/**
* @since 1.27.0 (Android)
*
* @property {number} width the surface width in device pixels.
* @property {number} height the surface height in device pixels.
* @property {number} deviceScaleFactor the number of device pixels per CSS pixel.
*/
type WebViewSurfaceSize = {
width: number;
height: number;
deviceScaleFactor: number;
}
/**
* @since 1.27.0 (Android)
*
* @property {number} width the requested surface width in device pixels.
* @property {number} height the requested surface height in device pixels.
* @property {number|undefined} deviceScaleFactor the requested number of device pixels per CSS pixel. If not provided, the default of `1` is used.
*/
type WebViewSurfaceSizeRequest = {
width: number;
height: number;
deviceScaleFactor?: number;
}
```
### **Network**
```javascript
/**
* @since 1.24.0 (Android), 1.8.0 (iOS)
*
* Whitelists one or more hostnames for outbound network access. The runtime
* performs the following verification steps for each hostname:
*
* 1. **Forward DNS + TXT** — verifies that `_acu.` carries a TXT record
* `v=base64(sha256(deployment_source || host))`, then resolves the hostname
* to one or more IP addresses (A/AAAA).
* 2. **Reverse DNS + TXT** — for each resolved IP, performs a PTR lookup to
* obtain the PTR hostname and verifies that `_acu.` carries a
* TXT record `v=base64(sha256(deployment_source || ptr_hostname))`.
*
* Where `deployment_source` is the raw 32-byte Substrate Account ID of the deployment's owner
* and `host` is the bare hostname (no scheme, port, or path, e.g. `example.com`).
*
* Both steps must pass for an IP to be added to the whitelist.
* Connections to non-whitelisted hosts are rate-limited.
*
* @param {string|string[]} hosts The hostname or array of hostnames to whitelist. Must be bare hostnames without scheme, port, or path.
*/
_STD_.network.whitelist(hosts);
```
Example scripts for computing the verification hash:
#### Node.js
```javascript
// npm install @polkadot/util-crypto
const { createHash } = require('crypto');
const { decodeAddress } = require('@polkadot/util-crypto');
function verificationHash(source, host) {
// Decode hex Account ID directly, or convert SS58 address to raw bytes
const sourceBytes = source.length === 64
? Buffer.from(source, 'hex')
: Buffer.from(decodeAddress(source));
// SHA-256 over concatenation of source bytes and UTF-8 encoded host
const digest = createHash('sha256')
.update(sourceBytes)
.update(host)
.digest();
// Return the hash as a base64 string
return digest.toString('base64');
}
```
#### Python
```python
# pip install substrate-interface
from substrateinterface.utils.ss58 import ss58_decode
def verification_hash(source: str, host: str) -> str:
# Decode hex Account ID directly, or convert SS58 address to raw bytes
source_bytes = bytes.fromhex(source) if len(source) == 64 else bytes.fromhex(ss58_decode(source))
# SHA-256 over concatenation of source bytes and UTF-8 encoded host
digest = hashlib.sha256(source_bytes + host.encode()).digest()
# Return the hash as a base64 string
return base64.b64encode(digest).decode()
```
### **Substrate functions**
```javascript
/**
* Calls the `fulfill` extrinsic on the target substrate chain.
* @param {string | string[]} nodes the node URL or array of node URLs.
* @param {string} payload the string representation of the fulfill payload.
* @param {object} extra an object with extra arguments. It needs to provide a `callIndex` which is the hex representation of the `fulfill` extrinsic's call index on the target substrate chain.
* @param {SubstrateSuccess} success the success callback.
* @param {SubstrateError} error the error callback.
*/
_STD_.chains.substrate.fulfill(nodes, payload, extra, success, error);
/**
* @callback SubstrateSuccess
* @param {string} operationHash the operation hash of the submitted extrinsic.
*/
type SubstrateSuccess = (operationHash) => void;
/**
* @callback SubstrateError
* @param {string[]} message an error message.
*/
type SubstrateError = (message) => void;
```
### **Substrate signer functions**
These functions select and use the key that signs **Substrate extrinsics** submitted by `_STD_.chains.substrate.fulfill(...)` and friends. To sign arbitrary data (e.g. authenticating HTTP requests from your deployment) use the [Signers](#signers) API instead.
:::caution `Unknown Substrate signer SECP256R1`
`setSigner` accepts the curve ids `'P256'`, `'SECP256K1'` and `'ED25519'`. Passing `'SECP256R1'` throws `Unknown Substrate signer SECP256R1` at startup. The P-256 / secp256r1 key is selected with `'P256'`. For general-purpose signing prefer `_STD_.signers.secp256r1.sign(...)`.
:::
```javascript
/**
* Sets the curve type to use when signing extrinsics.
* @param {'P256' | 'SECP256K1' | 'ED25519'} curveType Note: the P-256 (secp256r1) key is selected with 'P256'.
*/
_STD_.chains.substrate.signer.setSigner(curveType);
/**
* Signs a payload.
*
* @since 1.9.2 (version code 58)
*
* @param {string} payload Hex string to sign.
* @return {string} Hex string representing the signature.
*/
_STD_.chains.substrate.signer.sign(payload);
```
### **Substrate codec functions**
```javascript
/**
* Hashes the given string using blake2b 256 bit.
* @param {string} value
* @return {string} The blake2b hash of the input value.
*/
_STD_.chains.substrate.codec.blakeTwo256(value);
/**
* Encodes a number to the SCALE encoding.
* @param {number | string} value the number to encode.
* @param {8 | 32 | 64 | 128} bitSize the number's bit size.
* @return {string} Hex string representing the SCALE encoded number.
*/
_STD_.chains.substrate.codec.encodeUnsignedNumber(value, bitSize);
/**
* Encodes a number to the compact SCALE encoding.
* @param {number | string} value the number to encode.
* @return {string} Hex string representing the compact SCALE encoded number.
*/
_STD_.chains.substrate.codec.encodeCompactUnsignedNumber(value);
/**
* Encodes bytes to SCALE encoding.
* @param {string | ArrayBuffer} value hex string or an ArrayBuffer representing the bytes to encode.
* @return {string} Hex string representing the SCALE encoded bytes.
*/
_STD_.chains.substrate.codec.encodeBytes(value);
/**
* Encodes a boolean value to SCALE encoding.
* @param {boolean} value the boolean value to encode.
* @return {string} Hex string representing the SCALE encoded boolean.
*/
_STD_.chains.substrate.codec.encodeBoolean(value);
/**
* Encodes a substrate address to SCALE encoding.
* @param value the address to encode.
* @return {string} Hex string representing the SCALE encoded address.
*/
_STD_.chains.substrate.codec.encodeAddress(value);
/**
* Encodes a substrate address to a `MultiAddress` SCALE encoded vale.
* @param value the address to encode.
* @return {string} Hex string representing the SCALE encoded multi address.
*/
_STD_.chains.substrate.codec.encodeMultiAddress(value: string);
```
### **Substrate contract functions**
```javascript
/**
* Calls the `fulfill` extrinsic on a contract deployed on a chain integrating the substrate contract pallet (`pallet-contract`).
* @param {string | stirng[]} nodes the node URL or array of node URLs.
* @param {string} callIndex an hex string representing the call index of the `call` extrinsic of `pallet-contract`.
* @param {string} destination the contract address.
* @param {string} data the contract call arguments as an hex string.
* @param {object} extra objet containing additional arguments, it has to at least provide `refTime` and `proofSize` as string values. Additionally it can provide a `value` as a string representing the amount to transfer with the contract call, `method` as a string representing the method name to use instead of `fulfill` and `storageDepositLimit` as a string value. Example: `{ refTime: "3951114240", proofSize: "629760" }`.
* @param {SubstrateSuccess} success the success callback.
* @param {SubstrateError} error the error callback.
*/
_STD_.chains.substrate.contract.fulfill(
nodes,
callIndex,
destination,
data,
extra,
success,
error
);
/**
* Calls the `fulfill` extrinsic on a contract deployed on a chain integrating the substrate contract pallet (`pallet-contract`).
* @param {string | stirng[]} nodes the node URL or array of node URLs.
* @param {string} method a string representing the method name to call on the destination contract.
* @param {string} destination the contract address.
* @param {string} data the contract call arguments as an hex string.
* @param {object} extra objet containing additional arguments. It can provide a `blockNumber` as a string to sepcify at what lever to read from and `storageDepositLimit` as a string value.
* @param {SubstrateSuccess} success the success callback.
* @param {SubstrateError} error the error callback.
*/
_STD_.chains.substrate.contract.callView(
nodes,
method,
destination,
data,
extra,
success,
error
);
```
### **Substrate Gear functions**
```javascript
/**
* Sends a message to an active Gear program extrinsic on a chain integrating the Gear protocol.
* @param {string | stirng[]} nodes the node URL or array of node URLs.
* @param {string} callIndex an hex string representing the call index of the `gear.sendMessage` extrinsic.
* @param {string} destination the active program address.
* @param {string} data an hex string encoding the method and arguments to call on the program.
* @param {object} extra objet containing additional arguments, it has to provide `gasLimit` as a string, `value` as a string and `keepAlive` as a boolean. Example: `{ gasLimit: "2000000000", value: "0", keepAlive: true }`.
* @param {SubstrateSuccess} success the success callback.
* @param {SubstrateError} error the error callback.
*/
_STD_.chains.substrate.gear.sendMessage(
nodes,
callIndex,
destination,
data,
extra,
success,
error
);
```
### **Tezos functions**
```javascript
/**
* Calls the `fulfill` entrypoint on the Tezos Acurast Proxy contract.
* @param {string | string[]} nodes the node URL or array of node URLs.
* @param {any} payload the second argument for the `fulfill` entrypoint call on the Acurast Proxy contract. It represents a Michelson value that will be packed to bytes.
* @param {object} extra object with extra arguments, it has to at least provide the values for the `fee`, `gasLimit` and `storageLimit` as numbers. Additionally it can provide an `entrypoint` as a string to use instead of `fulfill`. Example: `{ fee: 1500, gasLimit: 3000, storageLimit: 0 }`.
* @param {TezosSuccess} success the success callback.
* @param {TezosError} error the error callback.
*/
_STD_.chains.tezos.fulfill(nodes, payload, extra, success, error);
/**
* Calls a custom entrypoint on a Tezos contract.
* @param {string | string[]} nodes the node URL or array of node URLs.
* @param {any} payload a Michelson value representing the arguments of the entrypoint being called.
* @param {object} extra object with extra arguments, it has to at least provide the values for the `fee`, `gasLimit` and `storageLimit` as numbers. Additionally it can provide an `entrypoint` as a string to use instead of `fulfill` and `destination` as a string for the contract address to use instead of the default Acurast Proxy contract. Example: `{ fee: 1500, gasLimit: 3000, storageLimit: 0 }`.
* @param {TezosSuccess} success the success callback.
* @param {TezosError} error the error callback.
*/
_STD_.chains.tezos.customCall(nodes, payload, extra, success, error);
/**
* @callback TezosSuccess
* @param {string} operationHash the operation hash of the submitted operation.
*/
type TezosSuccess = (operationHash) => void;
/**
* @callback TezosError
* @param {string[]} message an error message.
*/
type TezosError = (message) => void;
```
### **Tezos encoding functions**
```javascript
/**
* Packs the given micheline structure.
* @param value an object representing a micheline structure.
* @return {string} Hex string representing the packed value.
*/
_STD_.chains.tezos.encoding.pack(value);
/**
* Encodes the given micheline structure into a hex value that can be used as key for big map values.
* @param {object} value an object representing a micheline structure.
* @return {string} Hex string representing the script hash encoded value.
*/
_STD_.chains.tezos.encoding.encodeExpr(value);
```
### **Tezos message signing**
```javascript
/**
* Signs the given message and returns the signature.
*
* Before signing, the message is prepended with the utf8 bytes of the
* string 'acusig' and the script's ipfs hash ('acusig' + SCRIPT_HASH + message),
* then the resulting bytes are hashed with blake2b256.
*
* @param {string} message an hex string representing the bytes to sign
* @return {string} Hex string representing the signature
*/
_STD_.chains.tezos.signer.sign(message);
```
### **Ethereum functions**
```javascript
/**
* Calls `fulfill` on a ethereum contract.
*
* The `extra` argument is an object that can provide the following:
* - `methodSignature`: an optional string representing the method signature, if not provided `fulfill(bytes)` is used.
* - `gasLimit`: a string representing the transaction's gas limit, if not provided '9000000' is used.
* - `maxPriorityFeePerGas`: a string representing the transaction's maxPriorityFeePerGas, if not provided '0' is used.
* - `maxFeePerGas`: a string representing the transaction's maxFeePerGas, if not provided '0'.
*
* @param {string} url the node URL.
* @param {string} destination the contract's address.
* @param {string} payload a hex string representing the arguments for the method call.
* @param {object} extra object with extra arguments.
* @param {EthereumSuccess} success the success callback.
* @param {EthereumError} error the success callback.
*/
_STD_.chains.ethereum.fulfill(url, destination, payload, extra, success, error);
/**
* @callback EthereumSuccess
* @param {string} operationHash the operation hash of the submitted operation.
*/
type EthereumSuccess = (operationHash) => void;
/**
* @callback EthereumError
* @param {string[]} message an error message.
*/
type EthereumError = (message) => void;
/**
* @return {string} The processor's ethereum address for the current deployment.
*/
_STD_.chains.ethereum.getAddress();
```
### **Ethereum message signing**
```javascript
/**
* Signs the given message and returns the signature.
*
* Before signing, the message is prepended with the utf8 bytes of the
* string 'acusig' and the script's ipfs hash ('acusig' + SCRIPT_HASH + message),
* then the resulting bytes are hashed with Keccak256.
*
* @param {string} message an hex string representing the bytes to sign
* @return {string} Hex string representing the signature
*/
_STD_.chains.ethereum.signer.sign(message);
```
### **Ethereum ABI functions**
```javascript
/**
* Encodes the given value.
*
* @param {any} value A string, number or an array/object containing strings and numbers.
* @return {string} Hex string representing the encoded value.
*/
_STD_.chains.ethereum.abi.encode(value);
/**
* Encodes a numeric value.
*
* @param {number|string} value A number or a hex string representing a big integer.
* @param {number} bitLength A number specifying the bit length.
* @param {boolean} isNatural A boolean indicating if it is a natural number.
* @return {string} Hex string representing the encoded value.
*/
_STD_.chains.ethereum.abi.encodeNumeric(value, bitLength, isNatural);
/**
* Encodes an objects as a structure.
*
* @param {any} value A string, number or an array/object containing strings and numbers.
* @param {boolean} isDynamic A boolean indicating if it is a dynamic strucure.
* @return {string} Hex string representing the encoded value.
*/
_STD_.chains.ethereum.abi.encodeStruct(value, isDynamic);
```
### **Bitcoin functions**
```javascript
/**
* Returns the public key for the bitcoin chain.
*
* @since 1.5.0 (version code 28)
*
* @return {string} Hex string representing the public key
*/
_STD_.chains.bitcoin.getPublicKey();
/**
* Returns an extended public key for the given derivation path.
*
* @since 1.7.0 (version code 38)
*
* @param {string} version an hex string representing the bytes that will be prepended to the extended public key bytes before the base58check encoding
* @param {string} derivationPath the derivation path to use. Currently, the only valid value is "m/0/1".
* @return {string} String representing the extended public key
*/
_STD_.chains.bitcoin.getExtendedPublicKey(version, derivationPath);
```
### **Bitcoin message signing**
```javascript
/**
* Signs the given message and returns the signature.
*
* Before signing, the message is prepended with the utf8 bytes of the
* string 'acusig' and the script's ipfs hash ('acusig' + SCRIPT_HASH + message).
*
* @since 1.4.0 (version code 26)
*
* @param {string} message an hex string representing the bytes to sign
* @return {string} Hex string representing the signature
*/
_STD_.chains.bitcoin.signer.sign(message);
/**
* Signs the given message and returns the signature.
*
* @since 1.4.0 (version code 26)
*
* @param {string} message an hex string representing the bytes to sign
* @return {string} Hex string representing the signature
*/
_STD_.chains.bitcoin.signer.rawSign(message);
/**
* Hashes the given value using SHA256.
*
* @since 1.4.0 (version code 26)
*
* @param {string} value an hex string representing the bytes to hash
* @return {string} Hex string representing the sha256 hash
*/
_STD_.chains.bitcoin.signer.sha256(value);
/**
* Signs the given message with a key derived with the given derivation path and returns the signature.
*
* Before signing, the message is prepended with the utf8 bytes of the
* string 'acusig' and the script's ipfs hash ('acusig' + SCRIPT_HASH + message).
*
* @since 1.7.0 (version code 38)
*
* @param {string} message an hex string representing the bytes to sign
* @param {string} derivationPath the derivation path to use. Currently, the only valid value is "m/0/1"
* @return {string} Hex string representing the signature
*/
_STD_.chains.bitcoin.signer.signHD(message, derivationPath);
/**
* Signs the given message with a key derived with the given derivation path and returns the signature.
*
* @since 1.7.0 (version code 38)
*
* @param {string} message an hex string representing the bytes to sign
* @param {string} derivationPath the derivation path to use. Currently, the only valid value is "m/0/1"
* @return {string} Hex string representing the signature
*/
_STD_.chains.bitcoin.signer.rawSignHD(message, derivationPath);
```
### **Bitcoin utils functions**
```javascript
/**
* Derives the given extended public key.
*
* @since 1.7.0 (version code 38)
*
* @param {string} xpub a string representing the extended public key to derive
* @param {string} derivationPath the derivation path to use
* @return {string} Hex string representing the derivced public key
*/
_STD_.chains.bitcoin.utils.derivePublicKey(xpub, derivationPath);
/**
* Encodes the given bytes using base58check.
*
* @since 1.7.0 (version code 38)
*
* @param {string} value an hex string representing the bytes to encode
* @return {string} The base58check encoded value
*/
_STD_.chains.bitcoin.utils.base58CheckEncode(value);
/**
* Encodes the given bytes using base58.
*
* @since 1.7.0 (version code 38)
*
* @param {string} value an hex string representing the bytes to encode
* @return {string} The base58 encoded value
*/
_STD_.chains.bitcoin.utils.base58Encode(value);
/**
* Decodes the given base58check value.
*
* @since 1.7.0 (version code 38)
*
* @param {string} value a string representing the base58check value to decode
* @return {string} Hex string representing the decoded value
*/
_STD_.chains.bitcoin.utils.base58CheckDecode(value);
/**
* Decodes the given base58 value.
*
* @since 1.7.0 (version code 38)
*
* @param {string} value a string representing the base58 value to decode
* @return {string} Hex string representing the decoded value
*/
_STD_.chains.bitcoin.utils.base58Decode(value);
```
### **Aeternity functions**
```javascript
/**
* Calls `fulfill` on an aeternity contract.
*
* The `extra` argument is an object that can provide the following:
* - `functionName`: an optional string representing the method name, if not provided `fulfill` is used.
* - `gasLimit`: a string representing the transaction's gas limit, if not provided '25000' is used.
* - `gasPrice`: a string representing the transaction's gas price, if not provided '1000000000' is used.
*
* @since 1.3.32
*
* @param {string} url the node URL.
* @param {string} destination the contract's address.
* @param {[object]} payload an array of encoded values. The objects inside this array need to be constructed using the functions found under `_STD_.chains.aeternity.data`.
* @param {object} extra object with extra arguments.
* @param {AeternitySuccess} success the success callback.
* @param {AeternityError} error the success callback.
*/
_STD_.chains.aeternity.fulfill(
url,
destination,
payload,
extra,
success,
error
);
/**
* Returns the Aeternity address.
*
* @since 1.3.34 (version code 19)
*
* @return {string} The Aeternity address
*/
_STD_.chains.aeternity.getAddress();
/**
* @callback EthereumSuccess
* @param {string} operationHash the operation hash of the submitted operation.
*/
type AeternitySuccess = (operationHash) => void;
/**
* @callback EthereumError
* @param {string[]} message an error message.
*/
type AeternityError = (message) => void;
```
### **Aeternity data encoding functions**
```javascript
/**
* Returns an object representing an integer that can be used as payload in the `fulfill` call.
*
* @since 1.3.32
*
* @param {number | string} value a value representing an integer.
* @return {object} an object representing an integer that can be used as payload in the `fulfill` call.
*/
_STD_.chains.aeternity.data.int(value);
/**
* Returns an object representing a string that can be used as payload in the `fulfill` call.
*
* @since 1.3.32
*
* @param {string} value a string value.
* @return {object} an object representing a string that can be used as payload in the `fulfill` call.
*/
_STD_.chains.aeternity.data.string(value);
/**
* Returns an object representing bytes that can be used as payload in the `fulfill` call.
*
* @since 1.3.32
*
* @param {string} value an hex string representing the bytes.
* @return {object} an object representing bytes that can be used as payload in the `fulfill` call.
*/
_STD_.chains.aeternity.data.bytes(value);
/**
* Returns an object representing a list of objects that can be used as payload in the `fulfill` call.
*
* @since 1.3.32
*
* @param {object[]} values an array of objects that were created using the functions found under `_STD_.chains.aeternity.data`.
* @return {object} an object representing a list of objects that can be used as payload in the `fulfill` call
*/
_STD_.chains.aeternity.data.list(values);
/**
* Returns an object representing a tuple can be used as payload in the `fulfill` call.
*
* @since 1.3.32
*
* @param {object[]} values an array of objects that were created using the functions found under `_STD_.chains.aeternity.data`.
* @return {object} an object representing a tuple can be used as payload in the `fulfill` call.
*/
_STD_.chains.aeternity.data.tuple(values);
/**
* Returns an object representing a map can be used as payload in the `fulfill` call.
*
* The input value is an array of arrays of objects. The items need to be an array of size 2,
* where the first element represents a map key and the second element represents its value:
*
* _STD_.chains.aeternity.data.map([
* [_STD_.chains.aeternity.data.string("key1"), _STD_.chains.aeternity.data.string("value1")],
* [_STD_.chains.aeternity.data.string("key2"), _STD_.chains.aeternity.data.string("value2")]
* ]);
*
* @since 1.3.32
*
* @param {object[][]} values an array of arrays of objects that were created using the functions found under `_STD_.chains.aeternity.data`.
* @return {object} an object representing a map can be used as payload in the `fulfill` call.
*/
_STD_.chains.aeternity.data.map(values);
/**
* Returns an object representing an account pubkey can be used as payload in the `fulfill` call.
*
* @since 1.3.32
*
* @param {string} value a string representing an account pubkey .
* @return {object} an object representing an account pubkey can be used as payload in the `fulfill` call.
*/
_STD_.chains.aeternity.data.account_pubkey(value);
```
---
## Create Address
This guide explains how to create an address with [Talisman](https://www.talisman.xyz/wallet) wallet. To install the browser extensions, follow their setup instructions or checkout [their walkshrough video](https://www.youtube.com/watch?v=spSPykclJ8I).
:::info
Most wallets allow to create a generic polkadot address that is supported by Acurast, this guide focuses on the Talisman wallet.
:::
Once you created your wallet follow this steps to create an Acurast address:
1. In any view of the Talisman browser extension, choose **`Add Account`** from the sidebar and then click **`New Account`**.
2. Choose **`Polkadot`**.
3. Enter your preferred name and click **`Create ->`**.
4. The extensions will open up your newly created account and shows the name on the top-left. Open the dropdown menu next to Send/Receive and choose **`Copy address`**.
5. Choose **`Substrate (Generic)`**, filter the list to find the item if necessary.
6. In the popup, click **`Copy Address`**
Paste and store the address somewhere for later use.
7. Fund the account. On **Canary**, claim cACU from the [Acurast Faucet](https://faucet.acurast.com/). On **Mainnet**, ACU has no faucet — see [How to Get ACU](/token-holders/how-to-get-acu).
---
## First App Deployment (Hub)
## Introduction
This tutorial will guide you through deploying a simple application on Acurast.
After deploying any app, you are eligible to get the reward for the "Deploy for the Rebellion" quest on the [Acurast Cloud Rebellion](https://rebellion.acurast.com/).
:::tip
For developers, reading the [First App Deployment](/developers/deploy-first-app) tutorial is recommended.
:::
### Writing the code
Code has been prepared that you can deploy. It is a simple webserver that will return a "Hello, World!" message.
The code is located here: ipfs://Qmb7sw1mH349wNJsoGWRs1HedAwVXtHPsW4YoXok15DeZL
## Deploying the Application
1. Open up the [Acurast Hub](https://hub.acurast.com/) and click on "Create Deployments".
2. You can leave the default settings and scroll down to "Deployment Code". There you click on "IPFS URL" and paste the link from above.
3. Now scroll down. You can optionally select on which processor your app will be deployed to, for example one of your own. But you can also leave it with the default option.
4. In the Execution Schedule settings, you select "One-time" and set the End time to a time in the future.
5. Scroll down and click on "Suggest Reward", then click on "Publish Deployment".
Success! You've successfully deployed your first application on Acurast!
## Verify the Deployment
Once the start time is reached, your app will be available at `https://.acu.run`. (Note: processor addresses are all lowercase.). You can find the address of the processor that runs your app in the deployment details in the Acurast Hub. Open the "Deployments" view, then click on the deployment and look at the "Assigned Processors" section. Copy the "Main Acurast Account", this is the processor address that runs your app.
---
## First App Deployment
## Introduction
This tutorial will guide you through deploying a simple application on Acurast. By the end of this guide, you'll have your first project ready and deployment up and running.
:::tip
If you prefer to jump right in, you can take a look at one of the example projects:
- [Express Server on Acurast](https://github.com/Acurast/acurast-example-apps/tree/26ebfb27b1f0bdf4a146acafa792d47c155a34d5/apps/app-webserver)
- [Fetch data from an API](https://github.com/Acurast/acurast-example-apps/tree/26ebfb27b1f0bdf4a146acafa792d47c155a34d5/apps/app-fetch).
You can either clone those repositories, or set up a blank Acurast starter project by running `npx @acurast/cli new `.
:::
## Prerequisites
- Basic knowledge of Node.js and the Command Line
:::tip Other ways to deploy
This tutorial uses the [CLI](/developers/tools/cli) — the interactive path. You can also deploy programmatically with the [SDK](/developers/tools/sdk), or pay with USDC on Base via the [Deploy Agent](/developers/tools/deploy-agent).
:::
## Setting up the Project
The structure of a project looks exactly like a normal Node.js project, with one extra file: `acurast.json` — this configures the deployment and is covered below. See the [Deployment Config reference](/developers/build/deployment-config) for all fields.
### Writing the code
First, let's start by creating a simple node.js project. You can find the code of the example, including all the build steps and configurations, on [GitHub](https://github.com/Acurast/acurast-example-apps/blob/26ebfb27b1f0bdf4a146acafa792d47c155a34d5/apps/app-webserver/)
If you're interested only in the Acurast part of the tutorial, feel free to skip to the "Installing the Acurast CLI" step.
This app is a simple Express server that returns a "Hello, World!" message. This is the code of the app:
```typescript
/**
* WARNING: This subdomain is NOT secure and should not be used in production.
* Anyone can simply overwrite it with their own project and hijack requests
* This is simply for testing purposes. If you need a secure way to host your
* project, please reach out to us.
*/
const LOCALTUNNEL_SUBDOMAIN = ""; // This is the subdomain where your webserver will be available. Eg. https://example.acu.run
const LOCALTUNNEL_HOST = "https://proxy.acu.run/";
const LOCAL_PORT = 3000;
if (!LOCALTUNNEL_SUBDOMAIN) {
console.log("LOCALTUNNEL_SUBDOMAIN must be set");
process.exit(1);
}
const app = express();
app.use(express.json());
app.get("/", (req, res) => {
res.send(`Hello from Acurast!`);
});
app.listen(LOCAL_PORT, () =>
console.log(`Server listening on port ${LOCAL_PORT}!`)
);
const startTunnel = async () => {
const tunnel = await localtunnel({
subdomain: LOCALTUNNEL_SUBDOMAIN,
host: LOCALTUNNEL_HOST,
port: LOCAL_PORT,
});
console.log("Tunnel started at", tunnel.url);
};
startTunnel();
```
This code starts a webserver using express on port 3000, then starts a localtunnel tunnel to make the server publicly available.
Set the `LOCALTUNNEL_SUBDOMAIN` variable to specify where the server will be available. If set to `example`, the URL will be `https://example.acu.run`.
:::note
This localtunnel server is not secure and should not be used in production. Work is underway to make this secure by default, but if a secure way to host your project is needed now, please reach out via the community channels.
:::
### Building the project
To deploy a project to the Acurast Cloud, it needs to be bundled into a single js file. This example uses webpack. You can find the configuration in the example project on [GitHub](https://github.com/Acurast/acurast-example-apps/blob/26ebfb27b1f0bdf4a146acafa792d47c155a34d5/apps/app-webserver/)
Running `npm run bundle` will then output a single js file which includes all necessary dependencies.
The file is located in `dist/bundle.js`. It includes your code, as well as all the dependencies in a single file.
This is the file that will be deployed to the Acurast Cloud. You can run it locally with `node dist/bundle.js` to test it.
## Setting up the Acurast CLI
Now that the app is ready, the Acurast CLI needs to be set up. The CLI is a tool that allows you to deploy and manage your applications on the Acurast Cloud.
### Installation
Let's install the Acurast CLI globally using npm:
```bash
npm install -g @acurast/cli
```
To verify that the installation worked, you can run `acurast` in the terminal and it will show you the help page:
```text
tutorial % acurast
_ _ ____ _ ___
/ \ ___ _ _ _ __ __ _ ___| |_ / ___| | |_ _|
/ _ \ / __| | | | '__/ _` / __| __| | | | | | |
/ ___ \ (__| |_| | | | (_| \__ \ |_ | |___| |___ | |
/_/ \_\___|\__,_|_| \__,_|___/\__| \____|_____|___|
Usage: acurast [options] [command]
A cli to interact with the Acurast Network.
Options:
-v, --version output the version number
-h, --help display help for command
Commands:
deploy [options] [project] Deploy the current project to the Acurast platform.
init Create an acurast.json and .env file
live [options] [project] Run the code in a live code environment on a remote processor
open Open Acurast websites in your browser
help [command] display help for command
```
### Adding Acurast Config to the Project
The next step is to add the Acurast Config to the project. To do that, run the following command:
```bash
acurast init
```
This will start an interactive guide, which will create `acurast.json` and `.env` files.
```text
tutorial % acurast init
Initializing Acurast CLI
There is no .env file, creating one now...
.env file created. Visit https://github.com/Acurast/acurast-cli to learn more.
The CLI will use the following address: 5GNimXAQhayQq8m8SxJt3xQmG2L3pGzeTkHopx9iPnrS6uHP
Visit the faucet to get some tokens: https://faucet.acurast.com?address=5GNimXAQhayQq8m8SxJt3xQmG2L3pGzeTkHopx9iPnrS6uHP
No package.json file found. This is unusual. Are you sure you are in the right directory?
? Enter the name of the project: tutorial
? Should the app be run one time or in an interval? One Time
? Enter the duration (eg. 1s, 5min or 2h): 1min
? What is the bundled javascript file to run? dist/bundle.js
🎉 Successfully created "acurast.json" and ".env" files
You can deploy your app using 'acurast deploy'
```
The defaults work well for this example. For all available fields and meanings, see the [Deployment Config reference](/developers/build/deployment-config).
### Getting ready for Deployment
To deploy the application, one more step is needed: funding the account.
:::info Mainnet vs Canary
The faucet only works on **Canary** (cACU). On **Mainnet**, ACU must be acquired via an exchange or bridge — see **[How to Get ACU](/token-holders/how-to-get-acu)**.
:::
> [!TIP]
> You can import the mnemonic that was generated and stored in the .env file and import it in Talisman (Browser Extension) to access the same account in the [Web Console](https://hub.acurast.com/).
Let's get some tokens on your new account. You can run the `acurast deploy` command, which will check your balance, and displays the link to the Faucet page.
```text
tutorial % acurast deploy
Deploying project "tutorial"
Your balance is 0. Visit https://faucet.acurast.com?address=5GNimXAQhayQq8m8SxJt3xQmG2L3pGzeTkHopx9iPnrS6uHP to get some tokens.
```
Visit the link displayed in the CLI and follow the instructions to get some tokens. They should be available in a few seconds.
That's it! You're now ready to deploy your app.
## Deploying the Application
To deploy your application, run `acurast deploy`:
```text
tutorial % acurast deploy
Deploying project "tutorial"
The CLI will use the following address: 5GNimXAQhayQq8m8SxJt3xQmG2L3pGzeTkHopx9iPnrS6uHP
The deployment will be scheduled to start in 5 minutes 0 seconds.
There will be 1 executions with a cost of 0.001 cACU each.
❯ Deploying project (first execution scheduled in 246s)
✔ Submitted to Acurast (ipfs://Qmdk1zGq2h9SiMLUQN845rB9ii6YbpQXdFTHz3j8zXQp8C)
✔ Deployment registered (DeploymentID: 3,461)
⠇ Waiting for deployment to be matched with processors
◼ Waiting for processor acknowledgements
```
Congratulations, your deployment is now being registered in the network and executed soon! Check the CLI for more information about the deployment process.
:::tip Deploy using Deploy Agent (x402)
You can use our [Deploy Agent](/developers/tools/deploy-agent) to deploy jobs using USDC on EVM base instead.
:::
## Verifying the Deployment
If you followed this tutorial, then your app will be available at `https://.acu.run`. ("\" is the value you set for `LOCALTUNNEL_SUBDOMAIN` in the code).
Success! You've successfully deployed your first application on Acurast!
## Next steps
- **[Environment Variables](/developers/build/environment-variables)** — pass encrypted secrets (API keys, tokens) to your deployment.
- **[Node.js Runtime Environment](/developers/build/nodejs-runtime-environment)** — what's available to your Node.js script on the processor.
- **[Deployment Config](/developers/build/deployment-config)** — full `acurast.json` reference.
- **[Example Apps](/developers/examples)** — more projects to clone and learn from.
- **[CLI reference](/developers/tools/cli)** — deployment management, live code, key reuse.
Join the [Telegram](https://t.me/acurastnetwork) or [Discord](https://discord.gg/wqgC6b6aKe) to be part of the community!
---
## Run S3-Compatible Storage on Acurast
This example runs a single-node [Garage](https://garagehq.deuxfleurs.fr)
**S3-compatible object store** inside an Acurast Cargo deployment and exposes it
over the Acurast Tunnel's two connections:
- **primary** connection → the **S3 API** (port `3900`), usable by any S3 client at
`https://.acu.run` (path-style, region `garage`), and
- **secondary** connection → **SSH** for shell access.
The generated S3 access keys are delivered to you as a `credentials` webhook event.
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-cargo-garage
```

## 2. What's in the `app/` folder
| File | Purpose |
| --- | --- |
| `start.sh` | Entrypoint. **Phase 1:** installs SSH + tunnel deps, builds the `getifaddrs` shim, starts Dropbear and the tunnel. **Phase 2:** downloads the Garage binary, writes a single-node config, starts the server, creates the bucket + access key, and starts the S3 API. SSH comes up first so a stalled Phase 2 is still debuggable. |
| `tunnel.py` | Opens the reverse tunnel — primary → S3 API (`3900`), secondary → SSH (`2222`). |
| `getifaddrs_override.c` | PRoot shim. |
| `callback.sh` | POSTs `log` / `started` / `error` / `credentials` events to your `CALLBACK_URL`. |
## 3. (Optional) Use your own domain
By default the tunnel serves on `https://.acu.run`, with a Let's Encrypt
certificate provisioned automatically — nothing to set up. To use your own domain
suffix instead, do the one-time DNS setup (a wildcard record and an `_acu` TXT
record) from the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2) and set `DOMAIN_SUFFIX_MAINNET`/`_CANARY` below.
## 4. Configure `.env`
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Deployer seed phrase. **Never commit it.** |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables`. |
| `SSH_PASSWORD` | optional | Root SSH password. Defaults to `password` — set a strong value. |
| `GARAGE_BUCKET` | optional | Bucket created on first start (defaults to `bucket`). |
| `CALLBACK_URL` | optional | Lifecycle-event webhook — **carries the generated S3 keys.** Use [webhook.watch](https://webhook.watch). |
### Getting a `CALLBACK_URL` from webhook.watch
Open [webhook.watch](https://webhook.watch) for a unique inspector URL and paste it
into `CALLBACK_URL`. This matters more here than usual: the deployment POSTs a
`credentials` event containing the **access key and secret** for your bucket — you
read them straight out of the webhook.watch dashboard.

## 5. A glance at `acurast.json`
- `runtime: "Shell"` on a `proot-distro` Ubuntu image.
- `execution`: `onetime`, `maxExecutionTimeInMs: 14400000` (a 4-hour window).
- `minProcessorVersions.android: "1.26.0"` (tunnel support).
- `includeEnvironmentVariables`: `CALLBACK_URL`, `SSH_PASSWORD`,
`NETWORK`, `GARAGE_BUCKET`.
## 6. Deploy
```bash
npm i
npm run deploy # runs `acurast deploy`
```
The CLI shows the reward market and a **suggested price** — accept it and confirm.

Then watch webhook.watch. After the install `log` events, the **`credentials`**
event arrives with the region (`garage`), the bucket, and your `accessKeyId` /
`secretAccessKey`.

---
## Part 2 — Using the object store
You now have an S3 endpoint and keys. Any S3 client works — here's the AWS CLI
(note path-style and region `garage`):
```bash
export AWS_ACCESS_KEY_ID=GK... # from the credentials event
export AWS_SECRET_ACCESS_KEY=...
aws --endpoint-url https://.acu.run --region garage \
s3 cp ./file.txt s3://bucket/
aws --endpoint-url https://.acu.run --region garage \
s3 ls s3://bucket/
```
### Browse it with a web UI
Because the primary connection has a real Let's Encrypt certificate, any
browser-based S3 explorer can talk to it directly. Point it at the tunnel endpoint,
set region `garage`, enable **path-style**, and paste the keys from the
`credentials` event.

From there it's an ordinary object store — upload a file and preview it right in
the browser (served back through the tunnel via a presigned URL)…

…and it shows up in the bucket, downloadable and shareable.

A working S3 endpoint, served from a phone. Keep in mind: stored objects live in
the processor's ephemeral storage and are **lost when the deployment ends** — this
is disposable/demo storage, and the generated keys should be treated as secret.
---
## Run the Hermes AI Agent on Acurast
This example runs [Hermes](https://hermes-agent.org) — an open-source autonomous AI
agent by Nous Research — on an Acurast processor, exposed over the Acurast Tunnel
two ways at once:
- **primary** connection → the **Hermes WebUI** (HTTP on `8787`). Open the tunnel
URL in a browser for the full chat / sessions / scheduled-jobs UI.
- **secondary** connection → **SSH** (Dropbear on `2222`) for the `hermes` CLI or
debugging.
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-cargo-hermes
```

## 2. What's in the `app/` folder
| File | Purpose |
| --- | --- |
| `start.sh` | Entrypoint. **Phase 1:** installs Dropbear + git, builds the `getifaddrs` shim, starts SSH and the tunnel. **Phase 2:** runs the official Hermes installer, pins it to OpenRouter, starts the WebUI on loopback `8787` and the Hermes gateway (the cron scheduler). SSH comes up first so a slow Phase 2 is still debuggable. |
| `tunnel.py` | Opens the reverse tunnel — primary → WebUI (`8787`), secondary → SSH (`2222`). |
| `getifaddrs_override.c` | PRoot shim. |
| `callback.sh` | POSTs `log` / `started` / `error` (and `webui_password`) events to your `CALLBACK_URL`. |
## 3. (Optional) Use your own domain
By default the tunnel serves on `https://.acu.run`, with a Let's Encrypt
certificate provisioned automatically — nothing to set up. To use your own domain
suffix instead, do the one-time DNS setup (a wildcard record and an `_acu` TXT
record) from the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2) and set `DOMAIN_SUFFIX_MAINNET`/`_CANARY` below.
## 4. Configure `.env`
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Deployer seed phrase. **Never commit it.** |
| `OPENROUTER_API_KEY` | ✅ | Your [OpenRouter](https://openrouter.ai/keys) API key — Hermes is pinned to `provider=openrouter`. |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables`. |
| `HERMES_MODEL` | optional | OpenRouter model id (default `openai/gpt-4o-mini`). |
| `HERMES_WEBUI_PASSWORD` | optional | Protects the public WebUI. If unset, `start.sh` generates a strong one and reports it as the `webui_password` event — the URL is never left open. |
| `SSH_PASSWORD` | optional | Root SSH password. Defaults to `password` — set a strong value. |
| `CALLBACK_URL` | optional | Lifecycle-event webhook. Use [webhook.watch](https://webhook.watch). |
### Getting a `CALLBACK_URL` from webhook.watch
Open [webhook.watch](https://webhook.watch), grab the unique inspector URL, and
paste it into `CALLBACK_URL`. The `started` event delivers the WebUI URL and SSH
command — and, if you didn't set one, the auto-generated `HERMES_WEBUI_PASSWORD`
arrives as a `webui_password` event.

## 5. A glance at `acurast.json`
- `runtime: "Shell"` on a `proot-distro` Ubuntu image.
- `execution`: `onetime`, `maxExecutionTimeInMs: 14400000` (a 4-hour window — the
installer downloads a fair amount, so give it time).
- `minProcessorVersions.android: "1.26.0"` (tunnel support).
- `includeEnvironmentVariables`: `CALLBACK_URL`, `NETWORK`, `SSH_PASSWORD`,
`OPENROUTER_API_KEY`, `HERMES_MODEL`, `HERMES_WEBUI_PASSWORD`.
## 6. Deploy
```bash
npm i
npm run deploy # runs `acurast deploy`
```
The CLI shows the reward market and a **suggested price** — accept it and confirm.

Then watch webhook.watch. After the (fairly long) install, the `started` event
arrives with the WebUI URL and the SSH connect command.

---
## Part 2 — Talking to the agent
### Open the WebUI
Open the `url` from the `started` event and authenticate with your
`HERMES_WEBUI_PASSWORD` (or the auto-generated one from the `webui_password`
event). You land in the Hermes chat.

### Give it a recurring task
Hermes isn't just a chat box — the gateway runs a cron scheduler, so you can ask it
to do things on a schedule. In the demo it's asked to *"every day at 8AM, go to
Hacker News and summarize the top posts"*, and it confirms it created a recurring
job — which now shows up in the sidebar.

You can open the job to see its cron schedule, prompt, and run history — an
autonomous agent ticking away on a phone.

### SSH for the CLI
Need the `hermes` CLI? Run the `connect` command from the `started` event and
authenticate with `SSH_PASSWORD`:
```bash
ssh -o ProxyCommand='openssl s_client -quiet \
-servername .acu.run \
-connect .acu.run:443' \
root@
```
The session is ephemeral — memory and skills are **lost when the deployment
ends**.
---
## Example Apps
Ready-to-clone example projects and step-by-step walkthroughs showing what you can run on the Acurast Cloud. Full source for every example lives in the [acurast-example-apps](https://github.com/Acurast/acurast-example-apps) repository.
## Guided walkthroughs
Longer, screenshot-driven guides that take a real service from clone to a live deployment over the [Acurast Tunnel](/developers/getting-started/quickstart-tunnel). Start with the Tunnel example — every other one builds on it.
| Walkthrough | What you get |
| --- | --- |
| [Expose a Service to the Internet](/developers/examples/tunnel) | A public HTTPS web page + SSH from a processor — the base tunnel primitive |
| [Run Any Language](/developers/examples/languages) | Python, Go, Rust, Java and more on the Cargo runtime |
| [Run PostgreSQL](/developers/examples/postgres) | A PostgreSQL database with a browser SQL console and native `psql` |
| [Host a WordPress Site](/developers/examples/wordpress) | A full WordPress stack (Apache + PHP + MariaDB) at a public URL |
| [Run S3-Compatible Storage](/developers/examples/garage) | A single-node Garage S3 object store, usable by any S3 client |
| [Run a Minecraft Server](/developers/examples/minecraft) | A Minecraft Java server you connect to over SSH forwarding |
| [Run the Hermes AI Agent](/developers/examples/hermes) | The Hermes autonomous AI agent with its WebUI over the tunnel |
| [Run the OpenClaw AI Assistant](/developers/examples/openclaw) | The OpenClaw personal AI assistant with its Control UI over the tunnel |
| [Sign Requests from a Deployment](/developers/examples/signing-requests) | Authenticate calls to your backend with the deployment's on-chain P-256 key |
## Clone-and-run examples
Smaller projects, each showing a single feature of the Acurast Cloud. Full source in the [acurast-example-apps](https://github.com/Acurast/acurast-example-apps) repository.
| Example | What it shows |
| --- | --- |
| [app-cargo](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo) | Run a non-Node.js workload using the [Cargo](/developers/getting-started/quickstart-cargo) runtime |
| [app-env-vars](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-env-vars) | Reading encrypted environment variables (API keys, secrets) |
| [app-external-dependencies](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-external-dependencies) | Bundling npm dependencies into a deployment |
| [app-fetch](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-fetch) | Fetching data from an external API on a schedule |
| [app-heic-to-png](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-heic-to-png) | Image conversion workload |
| [llm](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-llm) | Run an LLM on Acurast |
| [p2p](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-p2p) | Connect to a processor over a P2P network |
| [app-puppeteer](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-puppeteer) | Headless browser automation |
| [app-telegram-bot](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-telegram-bot) | Long-running Telegram bot |
| [tunnel](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-tunnel) | Expose a local service to the public internet |
| [app-wasm](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-wasm) | Running WebAssembly modules inside a deployment |
| [app-webserver](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-webserver) | Express server exposed over `acu.run` via localtunnel — the canonical first app |
## Scaffold a new project
The Acurast CLI can create a blank starter project for you:
```bash
npx @acurast/cli new
```
See **[First App Deployment](/developers/deploy-first-app)** for the full walkthrough.
---
## Run Any Language on Acurast (Python, Go, Rust & More)
# Run Any Language on Acurast
**Node.js was never the limit. With Cargo, if it runs on Linux, it runs on Acurast — Python, Go, Rust, Java, and everything else.**
For a long time, deploying to Acurast meant writing JavaScript. The processors run Node.js, and that runtime is a great fit for a huge class of workloads: oracles, bots, scrapers, API glue. But it was also a ceiling. If your workload was a Python ML script, a Go networking daemon, or a Rust binary, you were out of luck.
Cargo removes that ceiling. It is Acurast's **Shell runtime**: instead of handing your code to a JavaScript engine, the processor boots a full Linux image, mounts your files, and runs an entrypoint script inside it. From there, you are no longer deploying "a Node.js app" — you are deploying a Linux workload. And a Linux workload can be written in any language you like.
> If it runs on Linux, it runs on Acurast.
To prove it, we built the same tiny program in **ten different languages** and deployed every one of them to the network. Each example lives in [`acurast-example-apps`](https://github.com/Acurast/acurast-example-apps) and does exactly one thing: POST a small JSON payload to a webhook, tagged with the language it came from. Boring on purpose — the interesting part is that the *same deployment model* carries all ten.
## How Cargo works
A Cargo deployment is just a directory of files plus an entrypoint. Three things make it tick, all declared in `acurast.json`:
```json
{
"runtime": "Shell",
"image": {
"url": "https://github.com/termux/proot-distro/releases/download/.../ubuntu-questing-aarch64-pd-v4.30.1.tar.xz",
"sha256": "5ab35b90cd9a9f180656261ba400a135c4c01c2da4b74522118342f985c2d328"
},
"fileUrl": "app",
"entrypoint": "start.sh"
}
```
- **`runtime: "Shell"`** tells the processor to run a script, not a JS bundle.
- **`image`** is a `proot-distro` Linux rootfs (here, Ubuntu on `aarch64`) — pinned by `sha256` so every processor runs the exact same bytes.
- **`fileUrl`** is the directory uploaded as your cargo; **`entrypoint`** is the script run inside the rootfs.
The processor downloads the image, mounts your `app/` directory, and runs `start.sh`. Everything after that is up to you. For the full walkthrough, see the [Cargo Quickstart](/developers/getting-started/quickstart-cargo) and the [Cargo runtime reference](/developers/build/cargo-runtime-environment).
## The pattern: bring your own toolchain
Every language example follows the same three-step shape inside `start.sh`:
1. **Prepare the rootfs** — set `PATH`/`HOME`, point `TMPDIR` at a writable dir, and configure DNS.
2. **Install the toolchain** — `apt-get install` the interpreter or compiler (or download a prebuilt tarball when apt doesn't carry it).
3. **Run the program.**
For an **interpreted** language like Python, that's nearly trivial — install and run:
```sh
apt-get install -y python3-requests
python3 "$SCRIPT_DIR/main.py"
```
A **compiled** language adds one step: build, then run. Go installs from apt and compiles on the device:
```sh
apt-get install -y golang-go
export CGO_ENABLED=0 # pure-Go DNS + runtime; no C toolchain needed
go run "$SCRIPT_DIR/main.go"
```
And when a language isn't reliably packaged, you just fetch the official build — exactly what you'd do on any Linux box. Zig, for example:
```sh
ZIG_VERSION=0.13.0
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-aarch64-${ZIG_VERSION}.tar.xz" -o "$HOME/zig.tar.xz"
tar -C "$HOME" -xf "$HOME/zig.tar.xz"
export PATH="$HOME/zig-linux-aarch64-${ZIG_VERSION}:$PATH"
zig build-exe "$SCRIPT_DIR/main.zig" -lc -lcurl -femit-bin="$HOME/zig-app"
"$HOME/zig-app"
```
That's the whole trick. There is no special integration — `apt`, `curl`, and `tar` are the integration. If you can install it on Linux, you can run it on Acurast.
## Per-language starting points
The repository ships the same webhook program in each language below. Same `acurast.json` shape, same `start.sh` skeleton — only the toolchain line differs. Pick the one closest to your stack, swap in your own program, and deploy.
### C++ {#cpp}
Installs `g++` from apt, compiles `main.cpp` on the device, and runs the binary.
```sh
apt-get install -y g++ libcurl4-openssl-dev
```
Example: [`app-cargo-cpp`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-cpp)
### C# {#csharp}
Uses the Mono compiler and runtime from apt.
```sh
apt-get install -y mono-mcs mono-runtime
```
Example: [`app-cargo-csharp`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-csharp)
### Go {#go}
Installs the Go toolchain from apt and compiles on the device. `CGO_ENABLED=0` keeps it pure-Go, so no C toolchain is needed.
```sh
apt-get install -y golang-go
export CGO_ENABLED=0
go run "$SCRIPT_DIR/main.go"
```
Example: [`app-cargo-go`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-go)
### Java {#java}
Installs the default JDK and runs a single source file directly (`java Main.java`).
```sh
apt-get install -y default-jdk
java "$SCRIPT_DIR/Main.java"
```
Example: [`app-cargo-java`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-java)
### Node.js {#nodejs}
Cargo can also run Node.js as an ordinary Linux workload — install it from apt like any other toolchain.
```sh
apt-get install -y nodejs
```
Example: [`app-cargo-nodejs`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-nodejs)
### PHP {#php}
Installs the PHP CLI and cURL extension from apt.
```sh
apt-get install -y php-cli php-curl
```
Example: [`app-cargo-php`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-php)
### Python {#python}
Installs the interpreter (with `requests`) from apt and runs the script — the simplest possible case.
```sh
apt-get install -y python3-requests
python3 "$SCRIPT_DIR/main.py"
```
Example: [`app-cargo-python`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-python)
### Ruby {#ruby}
Installs Ruby from apt and runs the script.
```sh
apt-get install -y ruby
```
Example: [`app-cargo-ruby`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-ruby)
### Rust {#rust}
Installs current stable Rust via `rustup` (apt's `rustc` is too old for some crates), then builds and runs.
```sh
curl -fsSL https://sh.rustup.rs | sh -s -- -y
```
Example: [`app-cargo-rust`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-rust)
### Zig {#zig}
apt doesn't carry Zig, so the example fetches the official prebuilt tarball, then compiles and runs.
```sh
ZIG_VERSION=0.13.0
curl -fsSL "https://ziglang.org/download/${ZIG_VERSION}/zig-linux-aarch64-${ZIG_VERSION}.tar.xz" -o "$HOME/zig.tar.xz"
tar -C "$HOME" -xf "$HOME/zig.tar.xz"
```
Example: [`app-cargo-zig`](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-zig)
## Seeing what happened
One catch comes with running off-device: the processor's stdout and stderr aren't accessible to you. So the examples include an **optional** debug block in `start.sh` that POSTs lifecycle and error reports to your webhook — `startup`, `done`, and on failure an `error` with the stage, exit code, and a tail of stderr:
```sh
report() {
curl -sS -X POST "$WEBHOOK_URL" -H "Content-Type: application/json" \
-d "{\"language\":\"python\",\"status\":\"$1\"${2:+,$2}}" >/dev/null 2>&1 || true
}
```
It is not required — the program already POSTs on success — but it turns a silent failure into a readable one. Strip it out once your workload is stable for a minimal deployment.
## Deploying it yourself
Every example deploys the same way:
```bash
cp .env.example .env # set ACURAST_MNEMONIC and WEBHOOK_URL
npm i
npm run deploy
```
The CLI uploads your `app/` directory, the processor pulls the pinned image, mounts the cargo, and runs `start.sh`. Within seconds your Python — or Rust, or Zig — is running on a real device somewhere on the network, POSTing back to your webhook.
## Why this matters
For builders, Cargo means you stop bending your workload to fit the platform. Bring the language, libraries, and binaries you already use. The deployment model is the same regardless of what's inside — which means the thing you learn once carries across every project you ship.
For the network, it widens the front door. The set of workloads that can run on Acurast is no longer "things expressible in JavaScript" — it's "things that run on Linux." That's most of the software ever written.
Node.js was the start. Cargo is what comes after.
---
## Run a Minecraft Server on Acurast
This example runs a **Minecraft Java server** inside an Acurast Cargo deployment
and exposes it over the Acurast Tunnel's two connections:
- **primary** connection → the Minecraft server port (`25565`), and
- **secondary** connection → **SSH** (Dropbear on `2222`).
Minecraft's wire protocol is raw TCP, not TLS — so the easy way to play is to SSH
in over the secondary connection and **local-forward** the game port (`ssh -L`).
The SSH session carries the raw TCP, no TLS wrapper needed on your machine.
:::note
Deploying this app **accepts the [Minecraft EULA](https://aka.ms/MinecraftEULA)** (`start.sh` writes `eula=true`).
:::
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-cargo-minecraft
```

## 2. What's in the `app/` folder
| File | Purpose |
| --- | --- |
| `start.sh` | Entrypoint. Installs a JDK + Dropbear, builds the `getifaddrs` shim, downloads the server jar, writes `eula=true` and a loopback-bound `server.properties`, starts the server on `127.0.0.1:25565` and SSH on `127.0.0.1:2222`. |
| `tunnel.py` | Opens the reverse tunnel — primary → Minecraft (`25565`), secondary → SSH (`2222`). |
| `getifaddrs_override.c` | PRoot shim. |
| `callback.sh` | POSTs `log` / `started` / `error` events to your `CALLBACK_URL`. |
## 3. (Optional) Use your own domain
By default the tunnel serves on `https://.acu.run`, with a Let's Encrypt
certificate provisioned automatically — nothing to set up. To use your own domain
suffix instead, do the one-time DNS setup (a wildcard record and an `_acu` TXT
record) from the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2) and set `DOMAIN_SUFFIX_MAINNET`/`_CANARY` below.
## 4. Configure `.env`
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Deployer seed phrase. **Never commit it.** |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables`. |
| `SSH_PASSWORD` | optional | Root SSH password. Defaults to `password` — set a strong value. |
| `MC_SERVER_URL` | optional | Override the server jar URL (vanilla/Paper/Fabric). Defaults to a pinned vanilla release. |
| `CALLBACK_URL` | optional | Lifecycle-event webhook. Use [webhook.watch](https://webhook.watch). |
### Getting a `CALLBACK_URL` from webhook.watch
Open [webhook.watch](https://webhook.watch), grab the unique inspector URL, and
paste it into `CALLBACK_URL`. The `started` event that lands there carries the SSH
`connect` command and the `forward` command you'll use to play.

## 5. A glance at `acurast.json`
- `runtime: "Shell"` on a `proot-distro` Ubuntu image.
- `execution`: `onetime`, `maxExecutionTimeInMs: 14400000` (a 4-hour window).
- `minProcessorVersions.android: "1.26.0"` (tunnel support).
- `includeEnvironmentVariables`: `CALLBACK_URL`, `NETWORK`, `SSH_PASSWORD`.
## 6. Deploy
```bash
npm i
npm run deploy # runs `acurast deploy`
```
The CLI shows the reward market and a **suggested price** — accept it and confirm.

Then watch webhook.watch. The install downloads a JDK and the server jar, so you'll
see a run of `log` events first…

…and finally the `started` event with the SSH `connect` and `forward` commands.
---
## Part 2 — Playing on the server
### Forward the game port over SSH
Run the `forward` command from the `started` event — it local-forwards `25565`
over the (TLS-wrapped) secondary SSH connection:
```bash
ssh -N -L 25565:127.0.0.1:25565 \
-o ProxyCommand='openssl s_client -quiet \
-servername .acu.run \
-connect .acu.run:443' \
root@
```
Leave it running.

### Add the server and join
In your Minecraft client, add a multiplayer server with the address
`127.0.0.1:25565` (that's your local end of the forward).

Join — and you're playing on a world hosted on a phone.


Your Minecraft client version must match the server jar version. The world lives
in ephemeral storage and is **lost when the deployment ends** — great for a
throwaway session with friends, not for a persistent survival world (yet).
---
## Run the OpenClaw AI Assistant on Acurast
This example runs [OpenClaw](https://openclaw.ai) — an open-source personal AI
assistant, "the AI that actually does things" — on an Acurast processor, exposed
over the Acurast Tunnel two ways at once:
- **primary** connection → the **OpenClaw Control UI** (HTTP on `18789`). Open the
tunnel URL in a browser for the full chat / sessions / config dashboard.
- **secondary** connection → **SSH** (Dropbear on `2222`) for the `openclaw` CLI
(e.g. `openclaw onboard` to link chat channels) or debugging.
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-cargo-openclaw
```

## 2. What's in the `app/` folder
| File | Purpose |
| --- | --- |
| `start.sh` | Entrypoint. **Phase 1:** installs Dropbear, builds the `getifaddrs` shim, starts SSH and the tunnel. **Phase 2:** installs Node.js + the `openclaw` npm package, writes a headless config (gateway on loopback `18789`, **password auth forced**, model pinned to OpenRouter), and starts the gateway that serves the Control UI. SSH comes up first so a slow Phase 2 is still debuggable. |
| `tunnel.py` | Opens the reverse tunnel — primary → Control UI (`18789`), secondary → SSH (`2222`). |
| `getifaddrs_override.c` | PRoot shim. |
| `callback.sh` | POSTs `log` / `started` / `error` (and `webui_password`) events to your `CALLBACK_URL`. |
## 3. (Optional) Use your own domain
By default the tunnel serves on `https://.acu.run`, with a Let's Encrypt
certificate provisioned automatically — nothing to set up. To use your own domain
suffix instead, do the one-time DNS setup (a wildcard record and an `_acu` TXT
record) from the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2) and set `DOMAIN_SUFFIX_MAINNET`/`_CANARY` below.
## 4. Configure `.env`
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Deployer seed phrase. **Never commit it.** |
| `OPENROUTER_API_KEY` | ✅ | Your [OpenRouter](https://openrouter.ai/keys) API key — OpenClaw is pinned to `provider=openrouter`. |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables`. |
| `OPENCLAW_MODEL` | optional | OpenRouter model id (default `openai/gpt-4o-mini`; the `openrouter/` prefix is added for you). |
| `OPENCLAW_GATEWAY_PASSWORD` | optional | Protects the public Control UI. If unset, `start.sh` generates a strong one and reports it as the `webui_password` event — the URL is never left open. |
| `SSH_PASSWORD` | optional | Root SSH password. Defaults to `password` — set a strong value. |
| `CALLBACK_URL` | optional | Lifecycle-event webhook. Use [webhook.watch](https://webhook.watch). |
### Getting a `CALLBACK_URL` from webhook.watch
Open [webhook.watch](https://webhook.watch), grab the unique inspector URL, and
paste it into `CALLBACK_URL`. The `started` event delivers the Control UI URL and
SSH command — and, if you didn't set one, the auto-generated
`OPENCLAW_GATEWAY_PASSWORD` arrives as a `webui_password` event.

## 5. A glance at `acurast.json`
- `runtime: "Shell"` on a `proot-distro` Ubuntu image.
- `execution`: `onetime`, `maxExecutionTimeInMs: 14400000` (a 4-hour window — the
install downloads a fair amount, so give it time).
- `minProcessorVersions.android: "1.26.0"` (tunnel support).
- `includeEnvironmentVariables`: `CALLBACK_URL`, `NETWORK`, `SSH_PASSWORD`,
`OPENROUTER_API_KEY`, `OPENCLAW_MODEL`, `OPENCLAW_GATEWAY_PASSWORD`.
## 6. Deploy
```bash
npm i
npm run deploy # runs `acurast deploy`
```
The CLI shows the reward market and a **suggested price** — accept it and confirm.

Then watch webhook.watch. The two-phase install shows up as `log` events —
"Phase 1: installing SSH + tunnel deps", "Phase 2: installing Node.js" — followed
by the `started` event with the Control UI URL and SSH command.

---
## Part 2 — Using the assistant
### Open the Control UI
Open the `url` from the `started` event. Because the tunnel forwards from loopback,
`start.sh` **forces password auth** so the public URL is never wide open — log in
with your `OPENCLAW_GATEWAY_PASSWORD` (or the auto-generated one from the
`webui_password` event).

Inside you get the full dashboard — chat, sessions, skills, cron jobs — and you can
talk to the assistant right away.

### SSH for the CLI
For first-time channel setup, SSH in and run `openclaw onboard`. OpenClaw connects
through chat apps (WhatsApp, Telegram, Discord, Slack, Signal); link a channel from
the Control UI or via onboarding. Run the `connect` command from the `started`
event and authenticate with `SSH_PASSWORD`:
```bash
ssh -o ProxyCommand='openssl s_client -quiet \
-servername .acu.run \
-connect .acu.run:443' \
root@
```
The session is ephemeral — config and memory are **lost when the deployment
ends**.
---
## Run PostgreSQL on Acurast
This example runs a real **PostgreSQL** server inside an Acurast Cargo deployment
and exposes it two ways over the Acurast Tunnel:
- a **browser SQL console** on the primary connection (open it in any browser), and
- **SSH** on the secondary connection, so you can local-forward port `5432` and
use native `psql`.
Postgres itself only ever listens on loopback — it's never directly reachable;
both paths reach it from inside the deployment.
:::warning
The web console has **no authentication** and runs arbitrary SQL. It's for disposable/test databases only. SSH access is gated by `SSH_PASSWORD`.
:::
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-cargo-postgres
```
## 2. What's in the `app/` folder
| File | Purpose |
| --- | --- |
| `start.sh` | Entrypoint. **Phase 1:** installs SSH + tunnel deps, builds the `getifaddrs` shim, starts Dropbear and the tunnel. **Phase 2:** installs Postgres, initializes the data dir, creates the database, and starts the web console. Runs SSH first so a stalled install is still debuggable. |
| `tunnel.py` | Opens the reverse tunnel — primary → web console (`8080`), secondary → SSH (`2222`). |
| `webadmin.py` | The browser SQL console served over the primary connection. |
| `getifaddrs_override.c` / `sysv_shm_override.c` | PRoot shims (Postgres needs the SysV shared-memory one). |
| `callback.sh` | POSTs `log` / `started` / `error` events to your `CALLBACK_URL`. |
## 3. (Optional) Use your own domain
By default the tunnel serves on `https://.acu.run`, with a Let's Encrypt
certificate provisioned automatically — nothing to set up. To use your own domain
suffix instead, do the one-time DNS setup (a wildcard record and an `_acu` TXT
record) from the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2) and set `DOMAIN_SUFFIX_MAINNET`/`_CANARY` below.
## 4. Configure `.env`
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Deployer seed phrase. **Never commit it.** |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables`. |
| `SSH_PASSWORD` | optional | Root SSH password. Defaults to `password` — set a strong value. |
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | optional | Superuser, password and DB created on first start. Set strong values. |
| `CALLBACK_URL` | optional | Lifecycle-event webhook. **Use [webhook.watch](https://webhook.watch).** |
### Getting a `CALLBACK_URL` from webhook.watch
Open [webhook.watch](https://webhook.watch) to get a unique inspector URL, and
paste it into `CALLBACK_URL`. The deployment POSTs its `log` / `started` events
there — including the web URL and the SSH connect command — so you never have to
dig through logs.

## 5. A glance at `acurast.json`
- `runtime: "Shell"` on a `proot-distro` Ubuntu image.
- `execution`: `onetime`, `maxExecutionTimeInMs: 7200000` (2-hour window).
- `minProcessorVersions.android: "1.26.0"` (tunnel support).
- `includeEnvironmentVariables`: `CALLBACK_URL`, `NETWORK`, `SSH_PASSWORD`,
`POSTGRES_USER/PASSWORD/DB`, `WEB_PORT`.
## 6. Deploy
```bash
npm i
npm run deploy # runs `acurast deploy`
```
The CLI shows the reward market and a **suggested price** — accept it and confirm.

Then watch webhook.watch. After the install `log` events you'll get the `started`
event with the web URL, the SSH `connect` command, and the `forward` command for
native `psql`.

---
## Part 2 — Using the database
### Option A: SSH in and run native `psql`
The `started` event includes a `forward` command that local-forwards `5432` over
SSH. Run it, then in another terminal:
```bash
psql -h 127.0.0.1 -p 5432 -U -d
```
### Option B: the browser SQL console
Open the `url` from the `started` event. You land on the SQL console — note the
red **"Insecure SQL console"** banner, a reminder that anyone with the URL has full
access.

From here it's just SQL. Create a table and insert some rows:
```sql
CREATE TABLE notes (id serial primary key, body text, created_at timestamptz default now());
INSERT INTO notes (body) VALUES ('hello acurast'), ('second row');
```

…and query them back:
```sql
SELECT * FROM notes ORDER BY id;
```

A full relational database, running on a phone, reachable from your browser over a
trusted TLS URL. Remember: the storage is ephemeral — everything is lost when the
deployment ends, so treat it as a disposable/test database.
---
## Signing Requests from a Deployment
A deployment often needs to call your own backend. How does the backend know the
request really came from a processor running *your* deployment, and not from
anyone who read your code? Every processor holds a set of per-deployment keys
whose public halves are recorded on-chain, so the deployment can **sign** each
request and the backend can **verify** it against the chain.
This example shows the working path with the P-256 (secp256r1) key in the
Node.js runtime, and the three details that trip people up:
1. The signer is `_STD_.signers.secp256r1`, but the public key is `getPublicKeys().p256`. Same key, different names.
2. `secp256r1.sign()` does **not** hash. Pass a 32-byte SHA-256 digest.
3. The signature is raw `r || s` (64 bytes), not DER.
:::caution Don't use `_STD_.chains.substrate.signer` for this
`_STD_.chains.substrate.signer.setSigner('SECP256R1')` throws
`Unknown Substrate signer SECP256R1`. That API selects the key used for
Substrate **extrinsics**, and its curve ids are `'P256'`, `'SECP256K1'`,
`'ED25519'`. For signing arbitrary bytes use `_STD_.signers.*` as shown below.
:::
## 1. Sign on the processor
```javascript title="deployment/index.js"
const { createHash } = require('crypto');
const BACKEND = 'https://api.example.com';
async function signedFetch(path, body) {
const timestamp = Date.now().toString();
const bodyText = JSON.stringify(body);
// Canonical message: method, path, timestamp, body. Keep it deterministic.
const message = ['POST', path, timestamp, bodyText].join('\n');
// ECDSA signers sign a *digest*: hash first, then sign the 32-byte hash.
const digestHex = createHash('sha256').update(message).digest('hex');
const signatureHex = _STD_.signers.secp256r1.sign(digestHex); // raw r||s, 64 bytes
// Compressed P-256 public key (33 bytes, hex). Also on-chain as SECP256r1.
const publicKeyHex = _STD_.job.getPublicKeys().p256;
return fetch(BACKEND + path, {
method: 'POST',
headers: {
'content-type': 'application/json',
'x-acurast-pubkey': publicKeyHex,
'x-acurast-timestamp': timestamp,
'x-acurast-signature': signatureHex,
},
body: bodyText,
});
}
signedFetch('/ingest', { temperature: 21.5 }).then((r) => console.log(r.status));
```
The hash step is also available without Node's `crypto` module as
`_STD_.chains.bitcoin.signer.sha256(hex)`, which takes and returns hex.
## 2. Verify on the server
Node's `crypto.verify` accepts the raw `r || s` encoding directly when
`dsaEncoding` is set to `ieee-p1363`. The compressed public key needs to be
wrapped in a SubjectPublicKeyInfo (SPKI) structure to become a `KeyObject`; the
snippet below builds it by hand so no extra dependency is needed.
```javascript title="server/verify.js"
const crypto = require('crypto');
// Compressed P-256 point -> SPKI DER -> KeyObject.
function p256KeyFromCompressedHex(hex) {
const point = Buffer.from(hex, 'hex');
if (point.length !== 33) throw new Error('expected 33-byte compressed P-256 key');
const spkiPrefix = Buffer.from(
'3039301306072a8648ce3d020106082a8648ce3d030107032200', // SEQ{ AlgId(ecPublicKey, prime256v1), BIT STRING(len 34) }
'hex'
);
return crypto.createPublicKey({
key: Buffer.concat([spkiPrefix, point]),
format: 'der',
type: 'spki',
});
}
function verifyRequest(req, rawBody, isRegisteredKey) {
const pubKeyHex = req.headers['x-acurast-pubkey'];
const timestamp = req.headers['x-acurast-timestamp'];
const signature = Buffer.from(req.headers['x-acurast-signature'], 'hex');
// 1. Only accept keys that belong to an active deployment of yours (see below).
if (!isRegisteredKey(pubKeyHex)) return false;
// 2. Reject stale requests.
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) return false;
// 3. Rebuild the exact message the processor signed and verify.
const message = ['POST', req.path, timestamp, rawBody].join('\n');
return crypto.verify(
'sha256',
Buffer.from(message),
{ key: p256KeyFromCompressedHex(pubKeyHex), dsaEncoding: 'ieee-p1363' },
signature
);
}
module.exports = { verifyRequest };
```
`crypto.verify('sha256', message, ...)` hashes `message` with SHA-256 and checks
the signature over that digest. That is why the processor side signs
`sha256(message)` and not `message`. If you instead see verification failures
with a correct key, this mismatch is the first thing to check.
## 3. Know which keys to trust
A valid signature only proves that *some* processor with *some* deployment key
signed the request. To know it was **your** deployment, the server needs the
set of public keys assigned to it. Two ways to get them:
- **Read the match record on-chain.** When a processor is matched to your
deployment, the assignment stored in the `acurastMarketplace` pallet includes
`pubKeys`, with the P-256 key under `SECP256r1`. It equals
`getPublicKeys().p256` byte for byte. Use the
[Acurast SDK](/developers/tools/sdk) or a Polkadot.js connection to read it.
- **Register on first contact.** Have the deployment call a registration
endpoint on start with its `getPublicKeys()`, and have the server cross-check
the key against the on-chain match record before storing it.
:::warning Match records are cleaned up
When a deployment ends and is cleaned up, its match records (including
`pubKeys`) are removed from chain state. Harvest and persist the keys you need
**while the deployment is active**. Do not rely on reading them back later.
:::
## Using the other curves
The same flow works with `secp256k1` (public key `getPublicKeys().secp256k1`,
raw low-`s` `r || s` output, no recovery byte) and with `ed25519`. Ed25519
differs in one way: it hashes internally, so pass the **full message** to
`_STD_.signers.ed25519.sign()`, not a digest, and verify with
`crypto.verify(null, message, key, signature)` on the server.
In the [Cargo runtime](/developers/build/cargo-runtime-environment#signer) the
equivalent call is the `signer_sign` RPC method with `curve: "p256"`. It has the
same digest-in, `r || s`-out behavior.
## Reference
- [Node.js Runtime Environment - Signers](/developers/build/nodejs-runtime-environment#signers)
- [Node.js Runtime Environment - Substrate signer functions](/developers/build/nodejs-runtime-environment#substrate-signer-functions)
- [Cargo Runtime Environment - Signer](/developers/build/cargo-runtime-environment#signer)
---
## Expose a Service to the Internet on Acurast
Acurast processors are phones. They sit behind carrier NAT with no public IP — so
how do you reach a service running on one from the open internet? The **Acurast
Tunnel** answers that: the processor opens an outbound reverse tunnel to a relay,
and the relay gives you a public HTTPS URL that forwards straight into your
deployment.
This first example is the simplest possible demonstration: it serves a static web
page **and** a Dropbear SSH server from a single Cargo deployment, each on its own
tunnel connection. Every other Cargo example in this series (Postgres, WordPress,
Garage, Minecraft, Hermes, OpenClaw) is built on exactly this pattern, so it is
the right place to start.
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-tunnel/cargo
```
## 2. What's in the `app/` folder
`app/` is the code that actually gets uploaded to the processor. It's small:
| File | Purpose |
| --- | --- |
| `start.sh` | The deployment **entrypoint**. Installs `dropbear` + Python, builds the `getifaddrs` shim, sets the SSH root password, starts a static web server (`python3 -m http.server`) on `127.0.0.1:8080` and SSH on `127.0.0.1:2222`, then launches `tunnel.py`. |
| `tunnel.py` | Opens the Acurast reverse tunnel — calls `tunnel_start` with `localAddr` (the web page) and `secondaryLocalAddr` (SSH), then reports the public URLs. |
| `getifaddrs_override.c` | A tiny C shim `LD_PRELOAD`ed to work around a PRoot quirk (see [PRoot Quirks](/developers/build/cargo-runtime-environment#proot-quirks)). |
| `callback.sh` | Helper that POSTs JSON lifecycle events (`log`, `started`, `error`) to your `CALLBACK_URL`. |
| `www/index.html` | The static page that gets served. |
The two-connection idea is the key: the **primary** connection gets a real
Let's Encrypt certificate (good for browsers); the **secondary** connection gets a
self-signed cert and is used here to carry raw SSH.
## 3. (Optional) Use your own domain
By default the tunnel serves your deployment on `https://.acu.run`, with a
Let's Encrypt certificate provisioned automatically — nothing to set up.
If you'd rather use your own domain suffix, it's a one-time DNS setup (a wildcard
record and an `_acu` TXT record) — follow the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2), then set `DOMAIN_SUFFIX_MAINNET`/`DOMAIN_SUFFIX_CANARY` below.
## 4. Configure `.env`
Copy the template and fill it in:
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Your deployer seed phrase (signs the on-chain deployment). **Never commit it.** |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match the `network` field in `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `DOMAIN_SUFFIX_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables` in `acurast.json`. |
| `SSH_PASSWORD` | optional | Root password for the SSH session. Defaults to `password` — set a strong value. |
| `CALLBACK_URL` | optional | Webhook that receives the lifecycle events. **Use [webhook.watch](https://webhook.watch).** |
### Getting a `CALLBACK_URL` from webhook.watch
You don't want to chase deployment logs to find your tunnel URL. Instead, open
[webhook.watch](https://webhook.watch), which instantly gives you a unique
inspector URL. Paste that URL into `CALLBACK_URL`. As the deployment runs, its
`log`, `started` and `error` events show up live in the webhook.watch dashboard.

## 5. A glance at `acurast.json`
`acurast.json` is the deployment config. You rarely need to touch it, but it's
worth knowing what it declares:
- `runtime: "Shell"` + `image` — runs your shell app inside a `proot-distro`
Ubuntu rootfs.
- `execution` — `onetime`, `maxExecutionTimeInMs: 7200000` (a 2-hour window; the
deployment shuts down after that).
- `minProcessorVersions.android: "1.26.0"` — the tunnel API needs a processor on
app version 1.26.0 or newer.
- `includeEnvironmentVariables` — the allowlist of `.env` vars forwarded to the
processor (`CALLBACK_URL`, `SSH_PASSWORD`, `NETWORK`).
- `startAt.msFromNow` — schedules the start a couple of minutes out.
## 6. Deploy
```bash
acurast deploy
```
The CLI shows the current reward market — a distribution of what processors are
charging — and a **suggested price**. Accept the suggested fee and confirm.

The deployment is registered on-chain and scheduled to start (per
`startAt.msFromNow`). Now watch your webhook.watch tab: first `log` events as the
processor installs dependencies, then the **`started`** event carrying your public
web URL and the SSH connect command.

---
## Part 2 — Using the tunnel
This is where each example diverges. For the plain Tunnel example there are two
things to try.
### Open the web page
Take the `url` from the `started` event and open it in a browser:
```
https://.acu.run
```
You get a page served *from the phone* over a real HTTPS certificate — no public
IP, no port forwarding, no reverse proxy of your own.

### SSH in over the secondary connection
SSH rides the secondary (self-signed) connection, so it's wrapped in TLS via an
`openssl s_client` ProxyCommand. The `started` event hands you the exact command:
```bash
ssh -o ProxyCommand='openssl s_client -quiet \
-servername .acu.run \
-connect .acu.run:8443' \
root@
```
Authenticate with your `SSH_PASSWORD` and you have a root shell inside the
deployment.
### What just happened
The full path is: your browser → Acurast relay → reverse tunnel → the processor's
sandbox → your service. The processor dialed out; nothing had to be opened
inbound — and yet you get a public, TLS-terminated URL for the web page and a
working SSH login, all from a phone.
That's the whole primitive. Everything else in this series just puts a more
interesting service behind `localAddr`.
---
## Host a WordPress Site on Acurast
This example runs a full **WordPress** stack — Apache + PHP + MariaDB — inside an
Acurast Cargo deployment and publishes it over the Acurast Tunnel:
- the **primary** connection serves Apache/WordPress at a real HTTPS URL, and
- the **secondary** connection carries **SSH** for shell access to the deployment.
## 1. Get the repo and open the example
```bash
git clone https://github.com/Acurast/acurast-example-apps.git
cd acurast-example-apps/apps/app-cargo-wordpress
```
## 2. What's in the `app/` folder
| File | Purpose |
| --- | --- |
| `start.sh` | Entrypoint. Installs Apache, PHP, MariaDB and Dropbear; builds the `getifaddrs` shim; initializes MariaDB and creates the DB/user; downloads WordPress core; starts Apache on `127.0.0.1:8080` and SSH on `127.0.0.1:2222`. |
| `wp-config.php` | WordPress config: takes DB creds from the environment and derives `WP_HOME`/`WP_SITEURL` from the request host, so it works at whatever tunnel URL it lands on. Trusts `X-Forwarded-Proto` so WordPress builds `https://` URLs. |
| `tunnel.py` | Opens the reverse tunnel — primary → WordPress (`8080`), secondary → SSH (`2222`). |
| `getifaddrs_override.c` | PRoot shim. |
| `callback.sh` | POSTs `log` / `started` / `error` events to your `CALLBACK_URL`. |
## 3. (Optional) Use your own domain
By default the tunnel serves on `https://.acu.run`, with a Let's Encrypt
certificate provisioned automatically — nothing to set up. To use your own domain
suffix instead, do the one-time DNS setup (a wildcard record and an `_acu` TXT
record) from the
[Tunnel Quick Start](/developers/getting-started/quickstart-tunnel)
(step 2) and set `DOMAIN_SUFFIX_MAINNET`/`_CANARY` below.
## 4. Configure `.env`
```bash
cp .env.example .env
```
| Variable | Required | What to set |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | ✅ | Deployer seed phrase. **Never commit it.** |
| `NETWORK` | ✅ | `canary` or `mainnet`. Must match `acurast.json`. |
| `DOMAIN_SUFFIX_MAINNET` / `_CANARY` | optional | Only for a custom domain. Leave unset to serve on `acu.run`. If set, use the one matching `NETWORK` and add it to `includeEnvironmentVariables`. |
| `SSH_PASSWORD` | optional | Root SSH password. Defaults to `password` — set a strong value. |
| `WORDPRESS_DB_NAME` / `_USER` / `_PASSWORD` | optional | Database created on first start. Set strong values. |
| `CALLBACK_URL` | optional | Lifecycle-event webhook. **Use [webhook.watch](https://webhook.watch).** |
### Getting a `CALLBACK_URL` from webhook.watch
Open [webhook.watch](https://webhook.watch), grab the unique inspector URL, and
paste it into `CALLBACK_URL`. The `started` event that arrives there carries the
public WordPress URL and the SSH connect command.

## 5. A glance at `acurast.json`
- `runtime: "Shell"` on a `proot-distro` Ubuntu image.
- `execution`: `onetime`, `maxExecutionTimeInMs: 7200000` (2-hour window).
- `minProcessorVersions.android: "1.26.0"` (tunnel support).
- `includeEnvironmentVariables`: `CALLBACK_URL`, `SSH_PASSWORD`,
`NETWORK`, `WORDPRESS_DB_NAME/USER/PASSWORD`.
## 6. Deploy
```bash
npm i
npm run deploy # runs `acurast deploy`
```
The CLI shows the reward market and a **suggested price** — accept it and confirm.

Then watch webhook.watch. The install is heavier than the plain tunnel (Apache,
PHP, MariaDB, WordPress core), so you'll see a run of `log` events first…

…and finally the `started` event with the public URL and SSH command.

---
## Part 2 — Setting up the site
### Run the install wizard
Open the `url` from the `started` event. Because it's a fresh install, WordPress
greets you with its setup wizard — pick a language and fill in the site details.

Set the site title, admin user and password, and finish the install.

### A live site
That's it — you have a real WordPress site running on a phone, reachable at a
trusted HTTPS URL.

Log in at `/wp-admin` and you get the full WordPress dashboard — publish posts,
install themes and plugins, everything.

### Shell access
Need to poke around? Run the `connect` command from the `started` event (it wraps
SSH over the secondary TLS connection) and authenticate with `SSH_PASSWORD`:
```bash
ssh -o ProxyCommand='openssl s_client -quiet \
-servername .acu.run \
-connect .acu.run:443' \
root@
```
Note: the database and uploads live in the processor's ephemeral storage and are
**lost when the deployment ends** — this is a disposable/demo site, not durable
hosting.
---
## Quickstart - Cargo
Cargo deployments run as native binaries inside a Linux distro image on the processor, isolated via [PRoot](https://proot-me.github.io/). Unlike the default Node.js runtime, Cargo gives you full access to the Linux environment: shell scripts, native tooling, and any language you can ship as a Linux binary.
## Prerequisites
### A 64-bit Android Core processor
Cargo deployments require a **64-bit (aarch64) Android device** running as an **Acurast Processor Core**. The Lite processor and iOS devices are not supported.
To set one up yourself:
1. Factory-reset a 64-bit Android phone (Android 12+).
2. Visit [Acurast Hub ↗](https://hub.acurast.com/) and connect your wallet.
3. Follow the **Processor Core** setup flow - the device will be locked down as a dedicated compute provider.
Full guide: **[Become a Compute Provider](/processors/become-compute-provider)**.
:::note
After onboarding, there is a warmup period of up to 3 epochs (~4.5 hours) before the device becomes eligible for public assignment matching. If you want to deploy on your freshly onboarded device before the warmup completes, it can be skipped by targeting the device directly with [Instant deploy](#instant-deploy).
:::
### Acurast CLI
Install the CLI globally:
```bash
npm install -g @acurast/cli
```
### ACU balance
Cargo deployments require ACU to cover execution costs. If you are deploying on **Mainnet**, you need real ACU. If you are deploying on **Canary**, claim free cACU from the **[faucet ↗](https://faucet.acurast.com)**.
---
## Step 1 - Create a project
:::note
`acurast new` generates a Node.js project with a file structure that does not apply to Cargo and can be skipped. Create the project directory yourself and run `acurast init` inside it instead:
:::
```bash
mkdir cargo-hello
cd cargo-hello
acurast init
```
This creates `acurast.json` (deployment config) and `.env` (secrets). See the **[CLI docs](/developers/tools/cli)** for all available commands and options.
---
## Step 2 - Write your Cargo app
:::info
The PRoot container starts with a minimal environment. The processor presets sensible defaults for `PATH`, `HOME`, and `/etc/resolv.conf` before your entrypoint runs, but these can be overridden in your script if your deployment needs different values. See **[PRoot Quirks](/developers/build/cargo-runtime-environment#proot-quirks)** for the exact defaults and known issues, including a `getifaddrs()` workaround for programs that inspect network interfaces.
:::
Create your app entrypoint file `acurast.sh`:
```bash title="acurast.sh"
#!/bin/sh
# PATH, HOME and /etc/resolv.conf are preset by the processor.
# Override them here if your deployment requires different values.
# TODO: Run your code
echo "Hello, world!"
```
### The bridge socket
The processor injects a `BRIDGE_SOCKET` environment variable at runtime. This is an abstract Unix domain socket that exposes host services - cryptographic signing, deployment metadata, and browser control - via a JSON-RPC 2.0 API.
Full API reference: **[Cargo Runtime Environment](/developers/build/cargo-runtime-environment)**.
---
## Step 3 - Configure for the Cargo (Shell) runtime
Open `acurast.json` and configure your project to use the `Shell` runtime. The `Shell` runtime is the foundation of Cargo - it boots a Linux distro image on the processor and runs your entrypoint script inside it.
Your app files (`fileUrl`) are extracted into the rootfs before the entrypoint runs. Structure them however you like - the only requirement is that the file named by `entrypoint` is a shell script and is placed at the root of the `fileUrl` directory or it's the `fileUrl` itself.
```json title="acurast.json"
{
"projects": {
"cargo-hello": {
"projectName": "cargo-hello",
"fileUrl": "acurast.sh",
"entrypoint": "acurast.sh",
"runtime": "Shell",
"image": {
"url": "https://github.com/termux/proot-distro/releases/download/v4.30.1/ubuntu-questing-aarch64-pd-v4.30.1.tar.xz",
"sha256": "5ab35b90cd9a9f180656261ba400a135c4c01c2da4b74522118342f985c2d328"
},
"network": "mainnet",
...
}
}
}
```
Key fields for the Cargo runtime:
| Field | Description |
| --- | --- |
| `runtime` | Must be `"Shell"` to enable the Cargo runtime |
| `image` | Linux distro image to boot - `url` (HTTPS `.tar.xz`) + `sha256` for verification. Must be an **aarch64** image |
| `entrypoint` | Script the processor runs after extracting the image |
| `fileUrl` | Path to the directory (or file) uploaded to the processor alongside the image |
For all other fields, see the **[CLI configuration reference](/developers/tools/cli#configuration-fields)**.
Supported distro images are listed in the [Termux proot-distro releases ↗](https://github.com/termux/proot-distro).
:::note
The Linux image is freshly unpacked for every execution - there is no persistent cache between runs. Any dependencies installed during one execution (e.g. via `apt-get`) will not be available in the next. Make sure your deployment schedule accounts for the setup time required at the start of each execution.
:::
### Instant deploy
To deploy directly to a specific device without waiting for the warmup period, use the `instantMatch` field inside `assignmentStrategy`. Each entry targets a processor by its SS58 address:
```json
{
"projects": {
"cargo-hello": {
...
"assignmentStrategy": {
"type": "Single",
"instantMatch": [
{
"processor": "",
"maxAllowedStartDelayInMs": 10000
}
]
},
"numberOfReplicas": 1,
...
}
}
}
```
Set `numberOfReplicas` to match the number of entries in `instantMatch`. You can find the processor address in the Acurast Hub after onboarding.
---
## Step 4 - Deploy
```bash
acurast deploy
```
The CLI uploads your `app/` directory to IPFS and registers the deployment on-chain. The processor downloads the distro image and your app, boots the container, and runs `start.sh`.
Monitor your deployment:
```bash
acurast deployments ls
```
---
## Example - Hello Cargo
This example uses Python to fetch the processor's public key and POST it to a webhook. It runs four times at 3-minute intervals.
Full source: **[acurast-example-apps/apps/app-cargo ↗](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo)**.
**Project layout:**
```
app/
start.sh
hello.py
acurast.json
.env
```
**`app/start.sh`** - sets up the environment, installs dependencies, and launches the Python script:
```bash title="app/start.sh"
#!/bin/sh
# Install necessary dependencies
apt-get update
apt-get install -y python3-requests
# Run the main application
python3 "$(dirname "$0")/hello.py"
```
**`app/hello.py`** - calls the bridge socket to get the signing public key, then POSTs it to a webhook:
```python title="app/hello.py"
BRIDGE_SOCKET = os.environ["BRIDGE_SOCKET"]
WEBHOOK_URL = os.environ["WEBHOOK_URL"]
def get_public_key() -> str:
request = json.dumps({
"jsonrpc": "2.0",
"method": "signer_publicKey",
"params": [{"curve": "p256"}],
"id": "1",
}) + "\n"
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock:
sock.connect("\0" + BRIDGE_SOCKET)
sock.sendall(request.encode())
response = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
response += chunk
if b"\n" in chunk:
break
return json.loads(response)["result"]["publicKey"]
def main():
public_key = get_public_key()
url = WEBHOOK_URL.rstrip("/") + "/hello"
resp = requests.post(url, json={"publicKey": public_key})
print(resp.status_code, resp.text)
if __name__ == "__main__":
main()
```
Add your webhook URL to `.env`:
```text title=".env"
ACURAST_MNEMONIC=abandon abandon about ...
WEBHOOK_URL=https://your-webhook.example.com
```
---
## Example - Cargo SSH
This example launches a Dropbear SSH server inside an Ubuntu rootfs and exposes it through a reverse tunnel (ngrok, bore, or pinggy - selectable via environment variable). Once the tunnel is established, the connection details are POSTed to a callback URL so you can SSH directly into the processor.
Full source: **[acurast-example-apps/apps/app-cargo-ssh ↗](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-cargo-ssh)**.
---
## Next steps
- **[Cargo Runtime Environment](/developers/build/cargo-runtime-environment)** - full RPC API reference (signing, encryption, browser control, deployment metadata).
- **[CLI docs](/developers/tools/cli)** - all commands, config fields, and environment variable encryption.
- **[Environment Variables](/developers/build/environment-variables)** - how to declare and encrypt secrets for your deployment.
- **[Deployment Config](/developers/build/deployment-config)** - complete `acurast.json` field reference.
---
## Quickstart - Tunnel
The Acurast reverse tunnel exposes a service running inside a deployment to the public internet, without the processor needing an inbound public IP or open ports.
The processor only makes **outbound** connections to a relay node, the relay then routes incoming traffic from external users back to the processor.
Once the tunnel is up, the deployment is reachable at `https://.acu.run` with a valid Let's Encrypt certificate provisioned automatically.
Optionally, it is possible to use a different domain instead of the `acu.run` suffix when properly configured.
## Prerequisites
### An Android Core processor
Tunnel deployments require Processor version 1.26.0.
Full guide: **[Become a Compute Provider](/processors/become-compute-provider)**.
### Acurast CLI
Install the CLI globally:
```bash
npm install -g @acurast/cli
```
### ACU balance
Tunnel deployments require ACU / cACU to cover execution costs. On **Canary**, claim free cACU from the **[faucet ↗](https://faucet.acurast.com)**.
### (Optional) A DNS suffix you control
It is possible to use the tunnel with a custom domain if proof of ownership is provided with a specific TXT DNS record described in Step 2.
---
## Step 1 - Create a project
```bash
npx @acurast/cli new my-tunnel-app
cd my-tunnel-app
acurast init
```
:::note
`acurast new` generates a Node.js project with a file structure that does not apply to Cargo and can be skipped. Create the project directory yourself and run `acurast init` inside it instead:
:::
```bash
mkdir cargo-tunnel
cd cargo-tunnel
acurast init
```
This creates `acurast.json` (deployment config) and `.env` (secrets). See the **[CLI docs](/developers/tools/cli)** for all available commands and options.
---
## (Optional) Step 2 - Configure DNS
In case of using a custom domain instead of `acu.run`, pick an owned subdomain (`tunnel.example.com` in the examples below) and add records at your DNS provider.
The domain needs to point to one or more relay nodes:
| Domain | IP |
| --- | --- |
| relay-1.mainnet.acurast.com | 82.220.91.110 |
| Domain | IP |
| --- | --- |
| relay-2.canary.acurast.com | 57.129.64.128 |
| canary-relay.5elementsnodes.com | 176.9.45.137 |
| relay.el9-acurast.com | 213.136.88.18 |
| canary-relay.vincent-acurast.xyz | 213.136.90.239 |
| canary-relay.acurast.online | 107.172.233.226 |
### Record 1 - wildcard pointing at the relays
The public URL of every deployment is `https://.`. The wildcard makes any `` resolve to the relays:
```
*.tunnel.example.com. A 82.220.91.110
*.tunnel.example.com. A ...
*.tunnel.example.com. A ...
```
### Record 2 - TXT record proving deployer ownership
The relay validates that whoever runs a deployment under your suffix also controls the suffix's DNS. Add a TXT record at `_acu.`:
```
_acu.tunnel.example.com. TXT ""
```
The value is `base64(sha256( || ))`, where `` is the 32-byte public-key bytes of the Acurast account that submits the deployment.
Use [ss58.org ↗](https://ss58.org) to convert your deployer SS58 address to its public-key hex, then compute the TXT value:
```bash
{ printf '%s' MY_ADDRESS_HEX_VALUE | xxd -r -p; printf '%s' MY_DOMAIN_SUFFIX; } | openssl dgst -sha256 -binary | base64
```
Replace `MY_ADDRESS_HEX_VALUE` with your deployer address's hex value and `MY_DOMAIN_SUFFIX` with your subdomain (e.g. `tunnel.example.com`).
If multiple deployer accounts share the same suffix, publish multiple TXT records on the same name, the relay accepts any matching record.
---
## Step 3 - Write your app
Your app opens the tunnel by calling `tunnel.start(spec)` with the relay addresses, your domain suffix, and the local port your service listens on. Once it returns `{ url, clientId, ... }`, the tunnel is live and external traffic is forwarded to your local port.
The Cargo runtime exposes the tunnel via a JSON-RPC bridge on the abstract Unix socket named in `$BRIDGE_SOCKET`. The example below opens the tunnel from Python and points it at whatever local service you choose to run alongside it.
:::info
Privileged ports (`< 1024`) cannot be bound inside the PRoot sandbox. Use ports >= 1024 for your local service. The public-facing port is always `443`.
:::
**`app/start.sh`** - installs Python and runs the tunnel script:
```bash title="app/start.sh"
#!/bin/sh
apt-get update
apt-get install -y python3 python3-cryptography
python3 "$(dirname "$0")/tunnel.py"
```
**`app/tunnel.py`** - generates a P-256 identity key and opens the tunnel via the bridge socket:
```python title="app/tunnel.py"
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
DOMAIN_SUFFIX = "acu.run"
TUNNEL_RELAYS = [
"relay-1.mainnet.acurast.com:4433",
]
LOCAL_ADDR = "127.0.0.1:8080" # point at your local service
BRIDGE_SOCKET = os.environ["BRIDGE_SOCKET"]
def rpc(method, params):
req = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1}
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.connect("\0" + BRIDGE_SOCKET)
s.sendall((json.dumps(req) + "\n").encode())
line = s.makefile("rb").readline()
s.close()
resp = json.loads(line)
if "error" in resp:
raise RuntimeError(resp["error"])
return resp["result"]
def primary_key_b64():
"""Generate a P-256 keypair as base64-encoded PKCS#8 DER.
Persist the bytes to disk and reload on subsequent starts to keep the
same `clientId` (and skip re-running ACME).
"""
key = ec.generate_private_key(ec.SECP256R1())
pkcs8 = key.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption(),
)
return base64.b64encode(pkcs8).decode("ascii")
info = rpc("tunnel_start", [{
"serverAddrs": TUNNEL_RELAYS,
"domainSuffix": DOMAIN_SUFFIX,
"localAddr": LOCAL_ADDR,
"primaryKey": {"algorithm": "Secp256r1", "bytes": primary_key_b64()},
"acmeStaging": False,
}])
print(f"Tunnel ready at: {info['url']}")
# Keep the process alive while the tunnel is up.
while True:
time.sleep(30)
```
For an end-to-end reference (dropbear SSH + callback events + cert reuse) see **[acurast-tunnel-proot ↗](https://github.com/Acurast/acurast-tunnel-proot)**.
Full tunnel API reference: **[Cargo Runtime Environment - Tunnel](/developers/build/cargo-runtime-environment#tunnel)**.
The Node.js runtime exposes the tunnel via `_STD_.tunnel.*` global callbacks. The example below starts a small HTTP server on `127.0.0.1:3000` and asks the tunnel to forward `https://.tunnel.example.com` to it.
```javascript title="index.js"
const http = require('http');
const DOMAIN_SUFFIX = 'acu.run';
const TUNNEL_RELAYS = [
'relay-1.mainnet.acurast.com:4433',
];
const LOCAL_ADDR = '127.0.0.1:3000';
// Generate or load a P-256 identity key as base64-encoded PKCS#8 DER.
// Persist this value across runs to keep the same `clientId` (and avoid
// re-running ACME on every restart).
const primaryKeyBase64 = '...';
const spec = {
serverAddrs: TUNNEL_RELAYS,
domainSuffix: DOMAIN_SUFFIX,
localAddr: LOCAL_ADDR,
primaryKey: {
algorithm: 'Secp256r1',
bytes: primaryKeyBase64,
},
acmeStaging: false,
};
http.createServer((_req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello from Acurast\n');
}).listen(3000, '127.0.0.1', () => {
print('HTTP server listening on 127.0.0.1:3000');
_STD_.tunnel.start(
spec,
(info) => print('Tunnel ready at: ' + info.url),
(err) => print('Tunnel failed: ' + err),
);
});
```
For a complete reference implementation, see the **[example apps ↗](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-tunnel)**.
Full tunnel API reference: **[Node.js Runtime Environment - Tunnel](/developers/build/nodejs-runtime-environment#tunnel)**.
:::note
The TUNNEL_RELAYS list needs to correspond to the DNS records configured on [Step 2](#step-2---configure-dns)
:::
---
## Step 4 - Configure `acurast.json`
```json title="acurast.json"
{
"projects": {
"cargo-tunnel": {
"projectName": "cargo-tunnel",
"fileUrl": "app",
"entrypoint": "start.sh",
"runtime": "Shell",
"image": {
"url": "https://github.com/termux/proot-distro/releases/download/v4.30.1/ubuntu-questing-aarch64-pd-v4.30.1.tar.xz",
"sha256": "5ab35b90cd9a9f180656261ba400a135c4c01c2da4b74522118342f985c2d328"
},
"network": "mainnet",
"requiredModules": ["Shell"],
"minProcessorVersions": {
"android": "1.26.0"
},
...
}
}
}
```
```json title="acurast.json"
{
"projects": {
"my-tunnel-app": {
"projectName": "my-tunnel-app",
"fileUrl": "index.js",
"entrypoint": "index.js",
"runtime": "NodeJS",
"network": "mainnet",
"minProcessorVersions": {
"android": "1.26.0"
},
...
}
}
}
```
For all other fields, see the **[CLI configuration reference](/developers/tools/cli#configuration-fields)**.
---
## Step 5 - Deploy
```bash
acurast deploy
```
The CLI uploads your app to IPFS and registers the deployment on-chain. The processor pulls it down, starts your app, and the app opens the tunnel.
Monitor your deployment:
```bash
acurast deployments ls
```
---
## Step 6 - Connect
Once the deployment is up and running, connect from anywhere.
For HTTP/HTTPS services:
```bash
curl https://.acu.run
```
The tunnel does TLS pass-through, so any protocol you can wrap in TLS works. For raw-TCP services like SSH, prefix with `openssl s_client` as a `ProxyCommand`:
```bash
ssh -o ProxyCommand='openssl s_client -quiet \
-servername .acu.run \
-connect .acu.run:443' \
root@
```
---
## Next steps
- **[Node.js Tunnel API reference](/developers/build/nodejs-runtime-environment#tunnel)** - full `_STD_.tunnel.*` surface.
- **[Cargo Tunnel API reference](/developers/build/cargo-runtime-environment#tunnel)** - full JSON-RPC surface.
- **[Example NodeJS deployment ↗](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-tunnel/nodejs)** - NodeJS reference with leader election + cert reuse.
- **[Example Cargo deployment ↗](https://github.com/Acurast/acurast-example-apps/tree/main/apps/app-tunnel/cargo)** - Cargo reference with dropbear SSH + callback events.
- **[Deployment Config](/developers/build/deployment-config)** - complete `acurast.json` reference.
---
## Quickstart
Three ways to get your first deployment running on Acurast. Pick the one that fits your workflow.
Best for local development and getting started.
```bash
# Scaffold a new project
npx @acurast/cli new my-project
cd my-project
# Initialize config (creates acurast.json and .env)
acurast init
# Fund the account shown by the CLI, then deploy:
acurast deploy
```
:::info Need ACU to deploy?
Mainnet (ACU) has no faucet — see **[How to Get ACU](/token-holders/how-to-get-acu)** for buying and bridging. On Canary (cACU), claim from the **[faucet](https://faucet.acurast.com)**.
:::
Full walkthrough: **[First App Deployment](/developers/deploy-first-app)** · Command reference: **[CLI docs](/developers/tools/cli)**.
Best for CI pipelines, backends, and custom deployment flows.
```bash
npm install @acurast/sdk
```
```typescript
const config = await loadAcurastConfig('./acurast.json')
await deployProject({ config })
```
Full API: **[SDK docs](/developers/tools/sdk)**.
Best for AI agents and services that pay in USDC on Base — no ACU account needed.
```bash
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"reward": 77136800000
}'
```
Full walkthrough: **[Deploy Agent docs](/developers/tools/deploy-agent)**.
## Next steps
- **[First App Deployment](/developers/deploy-first-app)** — step-by-step tutorial with a real Express server.
- **[Example Apps](/developers/examples)** — curated list of ready-to-clone projects.
- **[Node.js Runtime Environment](/developers/build/nodejs-runtime-environment)** — APIs available inside your Node.js deployment.
- **[Deployment Config](/developers/build/deployment-config)** — full `acurast.json` reference.
- **[Networks](/acurast-protocol/networks)** — RPCs, explorers, and faucet availability per network.
- **[How to Get ACU](/token-holders/how-to-get-acu)** — buying, bridging, and funding mainnet accounts.
---
## Developers
# Build on Acurast
Deploy serverless workloads to a global network of smartphone-based compute. Run anything from REST APIs to LLM inference, paid in ACU or USDC.
## Start here
- **[Quickstart](/developers/getting-started/quickstart)** — three entry paths (CLI, SDK, Deploy Agent). Pick one and ship in minutes.
- **[Deploy Your First App](/developers/deploy-first-app)** — full walkthrough with a real Express server.
- **[Example Apps](/developers/examples)** — curated, ready-to-clone projects.
## Build
- **[Deployment Config](/developers/build/deployment-config)** — full `acurast.json` reference.
- **[Environment Variables](/developers/build/environment-variables)** — secrets and runtime config.
- **[Node.js Runtime Environment](/developers/build/nodejs-runtime-environment)** — APIs available inside your Node.js deployment.
- **[Cargo Runtime Environment](/developers/build/cargo-runtime-environment)** — RPC API available inside your Cargo deployment.
- **[LLM on Acurast](/developers/build/llm-on-acurast)** — run open-weight models on phones.
## Tools
- **[Acurast CLI](/developers/tools/cli)** — scaffold, configure, deploy.
- **[SDK](/developers/tools/sdk)** — programmatic deploys for CI and backends.
- **[Deploy Agent](/developers/tools/deploy-agent)** — pay-per-deploy via x402 / USDC, no ACU account needed.
- **[DevTools](/developers/tools/devtools)** — local debugging utilities.
## Reference
- **[Protocol Architecture](/acurast-protocol/architecture/architecture)** — how deployments flow through the network.
- **[Matcher](/acurast-protocol/architecture/architecture#matcher)** — liquid matching, deployments, compute costs.
- **[Networks](/acurast-protocol/networks)** — endpoints for Mainnet and Canary.
## Related
- Need compute? See **[Compute Providers](/processors)** for capacity context.
- Pricing & token mechanics? See **[Acurast Token](/discover/acurast-token)**.
---
## Acurast CLI
:::tip Which tool should I use?
- **CLI** — interactive local workflow: `new` / `init` / `deploy` / `live`. Best for getting started and day-to-day deployments.
- **[SDK](/developers/tools/sdk)** — programmatic deploys from your own TypeScript/JavaScript code (CI, backends, custom flows).
- **[Deploy Agent](/developers/tools/deploy-agent)** — pay with USDC on Base via x402. Best for AI agents and services without ACU accounts.
:::
## Introduction
The Acurast CLI is a command-line tool to deploy and manage apps on the Acurast Cloud. It is built on top of [`@acurast/sdk`](/developers/tools/sdk), so any deployment workflow available in the CLI can also be driven programmatically from your own code.
## Installation
Install the Acurast CLI globally using npm:
```bash
npm install -g @acurast/cli
```
## Quick Start
```bash
# Create a new project from a template
acurast new my-project
# Initialize configuration (creates acurast.json and .env)
acurast init
# Deploy to Acurast Cloud
acurast deploy
```
## Commands
| Command | Description |
| --- | --- |
| `new ` | Create a new Acurast project from a template |
| `deploy [project]` | Deploy the current project to the Acurast platform |
| `estimate-fee [project]` | Estimate the fee for the current project |
| `deployments [arg]` | List, view, and manage deployments |
| `live [project]` | Run your project on a live-code-processor in real time |
| `init` | Create an `acurast.json` and `.env` file |
| `devtools ` | Request a DevTools view key and open the DevTools URL |
| `open` | Open Acurast resources in your browser |
| `help [command]` | Display help for a command |
### Options
- `-v`, `--version` — Output the version number
- `-h`, `--help` — Display help for a command
## Configuration
Running `acurast init` creates two files:
### acurast.json
This file defines your deployment parameters. Example:
```json
{
"projects": {
"example": {
"projectName": "example",
"fileUrl": "dist/bundle.js",
"network": "mainnet",
"onlyAttestedDevices": true,
"enableDevtools": false,
"assignmentStrategy": {
"type": "Single"
},
"execution": {
"type": "onetime",
"maxExecutionTimeInMs": 10000
},
"maxAllowedStartDelayInMs": 10000,
"usageLimit": {
"maxMemory": 0,
"maxNetworkRequests": 0,
"maxStorage": 0
},
"numberOfReplicas": 64,
"requiredModules": [],
"minProcessorReputation": 0,
"maxCostPerExecution": 100000000000,
"includeEnvironmentVariables": [],
"processorWhitelist": [],
"mutability": "Immutable",
"reuseKeysFrom": null
}
}
}
```
#### Configuration Fields
| Field | Description |
| --- | --- |
| `projectName` | The name of the project |
| `fileUrl` | Path to the bundled file including all dependencies (e.g., `dist/bundle.js`) |
| `network` | Network for deployment (e.g., `mainnet`, `canary`) |
| `onlyAttestedDevices` | Only allow attested devices to run the app |
| `enableDevtools` | Enable [DevTools](/developers/tools/devtools) for the deployment. Defaults to `false` |
| `startAt` | Start time — either `{ msFromNow: number }` or `{ timestamp: number }` |
| `assignmentStrategy` | `"Single"` (one set of processors) or `"Competing"` (new processors per execution) |
| `execution` | `"onetime"` or `"interval"` with `intervalInMs`, `numberOfExecutions`, and `maxExecutionTimeInMs` |
| `maxAllowedStartDelayInMs` | Maximum allowed start delay in milliseconds |
| `usageLimit` | Limits for `maxMemory`, `maxNetworkRequests`, and `maxStorage` (in bytes) |
| `numberOfReplicas` | How many processors run the deployment in parallel |
| `requiredModules` | Modules the processor must support. Supported values: `"DataEncryption"`, `"LLM"`, `"Shell"`. Defaults to `[]`. When `runtime` is `"Shell"`, `"Shell"` is auto-injected |
| `runtime` | Runtime used to execute the deployment. `"NodeJSWithBundle"` (default), `"NodeJS"`, or `"Shell"`. See [Shell Runtime](#shell-runtime) |
| `image` | Linux distro image used by the Shell runtime. Required when `runtime` is `"Shell"`. Object: `{ url, sha256 }` (HTTPS `.tar.xz` URL + SHA256). Ignored otherwise |
| `entrypoint` | Script or binary the processor runs after extracting the Shell image (e.g. `acurast.sh`) |
| `restartPolicy` | `"no"` (default — run once, no retry) or `"onFailure"` (retry up to 3 times on failure; only the third failure is reported) |
| `minProcessorReputation` | Minimum required processor reputation |
| `maxCostPerExecution` | Maximum cost per execution in the smallest denomination of ACU |
| `includeEnvironmentVariables` | Environment variables from `.env` to pass to the deployment |
| `processorWhitelist` | Whitelist of processor addresses |
| `minProcessorVersions` | Minimum processor versions (`android`, `ios`) |
| `mutability` | `"Immutable"` (default) or `"Mutable"` — controls whether the deployment can be modified after creation |
| `reuseKeysFrom` | Reuse keys from a previous mutable deployment. Format: `["Acurast", "address", deploymentId]` |
### .env
Stores secrets and environment variables:
```text
ACURAST_MNEMONIC=abandon abandon about ...
# ACURAST_IPFS_URL=https://api.pinata.cloud
# ACURAST_IPFS_API_KEY=eyJhb...
# ACURAST_RPC=wss://...
```
| Variable | Required | Description |
| --- | --- | --- |
| `ACURAST_MNEMONIC` | Yes | Mnemonic for the deployer account. Must have ACU (or cACU on canary). Claim cACU on the [faucet](https://faucet.acurast.com) |
| `ACURAST_IPFS_URL` | No | IPFS gateway URL (e.g., `https://api.pinata.cloud`) |
| `ACURAST_IPFS_API_KEY` | No | API key for the IPFS gateway. [Register here](https://pinata.cloud/) |
| `ACURAST_RPC` | No | Custom RPC URL |
## Environment Variables
You can pass encrypted environment variables to your deployments. They are encrypted during deployment and only decrypted on the processor at runtime.
**1. Add variables to `.env`:**
```text
API_KEY=your-api-key
```
**2. Reference them in `acurast.json`:**
```json
{
"includeEnvironmentVariables": ["API_KEY"]
}
```
**3. Access them in your code:**
```typescript
const API_KEY = _STD_.env[API_KEY];
```
For interval-based deployments, you can update environment variables between executions:
```bash
acurast deployments --update-env-vars
```
## Deployment Management
### Listing and Viewing
```bash
# List all deployments
acurast deployments ls
# List deployments on canary network
acurast deployments ls --network canary
# View a specific deployment
acurast deployments
```
### Cleanup
```bash
# Clean up old, finished deployments and return unused funds
acurast deployments --cleanup
# Clean up a specific deployment
acurast deployments --cleanup
```
### Updating Mutable Deployments
Deployments created with `"mutability": "Mutable"` can be updated after creation.
#### Update Script
```bash
acurast deployments update script [--dry-run] [--force]
```
Example:
```bash
acurast deployments update script \
"Acurast:5CiPPse...DjL:123456" \
"ipfs://QmNewScriptHash"
```
#### Transfer Editor Permissions
```bash
acurast deployments update editor [--dry-run] [--force]
```
### Deployment ID Format
Deployment IDs follow the format `origin:address:number`:
- **origin** — The chain name (currently `"Acurast"`)
- **address** — The deployer's AccountId32 address
- **number** — Sequential deployment number
Example: `"Acurast:5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL:123456"`
Find your deployment IDs by running `acurast deployments ls`.
## Live Code
The Live Code feature lets you run your code on a dedicated processor in real time, with immediate feedback including `console.log` output and errors.
### Setup
```bash
acurast live --setup
```
During setup, choose the duration and follow the CLI instructions. After the deployment starts (approximately 5 minutes), run:
```bash
acurast live
```
This executes your project on the live processor using the configuration from `acurast init`.
## Shell Runtime
Set `runtime: "Shell"` to run a deployment as a native binary inside a Linux distro image on the processor (PRoot-isolated). This unlocks shell scripts, native tooling, and any language you can ship as a Linux binary.
Provide an `image` (URL + SHA256) and an `entrypoint`. The CLI embeds the image reference in the deployment manifest and auto-adds the `"Shell"` required module on-chain.
```json
{
"projects": {
"my-shell-project": {
"projectName": "my-shell-project",
"fileUrl": "./shell-app",
"entrypoint": "acurast.sh",
"runtime": "Shell",
"image": {
"url": "https://github.com/termux/proot-distro/releases/download/v4.30.1/ubuntu-questing-aarch64-pd-v4.30.1.tar.xz",
"sha256": "5ab35b90cd9a9f180656261ba400a135c4c01c2da4b74522118342f985c2d328"
},
"restartPolicy": "no",
"network": "mainnet",
"onlyAttestedDevices": true,
"assignmentStrategy": { "type": "Single" },
"execution": { "type": "onetime", "maxExecutionTimeInMs": 60000 },
"maxAllowedStartDelayInMs": 10000,
"usageLimit": { "maxMemory": 0, "maxNetworkRequests": 0, "maxStorage": 0 },
"numberOfReplicas": 1,
"requiredModules": [],
"minProcessorReputation": 0,
"maxCostPerExecution": 5000000000,
"includeEnvironmentVariables": [],
"processorWhitelist": []
}
}
}
```
Supported images: see [Termux proot-distro releases](https://github.com/termux/proot-distro).
Environment variables in `includeEnvironmentVariables` are injected as standard system env vars (no `_STD_` API). Host services (deployment metadata, signing, browser control) are exposed via a JSON-RPC API on an abstract Unix socket; the socket name is in the `BRIDGE_SOCKET` env var.
## Key Reuse
The `reuseKeysFrom` field lets you maintain the same cryptographic keys across deployments. The referenced deployment must be mutable.
```json
{
"reuseKeysFrom": [
"Acurast",
"5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
123456
]
}
```
---
## Deploy Agent (x402)
:::tip Which tool should I use?
- **[CLI](/developers/tools/cli)** — interactive local workflow with ACU.
- **[SDK](/developers/tools/sdk)** — programmatic deploys from TypeScript/JavaScript with ACU.
- **Deploy Agent** — pay with USDC on Base via x402. Best for AI agents and services without ACU accounts.
:::
The Acurast Deploy Agent lets developers deploy computational jobs to the Acurast decentralized processor network directly from EVM chains (starting with USDC on Base).
Via the deploy agent's HTTP API, a simple [x402](https://docs.cdp.coinbase.com/x402/core-concepts/how-it-works) payment together with either a job specification or a VPS request will trigger the deployment for you.
This approach is ideal for:
- **AI agents** using [agentic wallets](https://docs.cdp.coinbase.com/agentic-wallet/welcome) to deploy Acurast jobs from LLM clients
- **Backend services** that need to programmatically deploy Acurast jobs without interacting with the Acurast parachain or managing ACU accounts
- **Anyone who wants a public SSH-reachable Ubuntu VPS** on a decentralized processor — see [Deploy a VPS](#deploy-a-vps-ssh-tunnel) below
Both approaches are outlined below.
## Supported Chains
| Chain | Chain ID | Token |
| ----- | -------- | ----- |
| Base | 8453 | USDC |
## Use via LLM
```shell
npx skills add coinbase/agentic-wallet-skills
```
Recommended skills are at least:
```txt
◆ Select skills to install (space to toggle)
│ ◼ authenticate-wallet (Sign in to the wallet. Use when you or the user want to l...)
│ ◼ fund (Add money to the wallet. Use when you or the user want to...)
│ ◻ monetize-service
│ ◼ pay-for-service (Make a paid API request to an x402 endpoint with automati...)
│ ◻ query-onchain-data
│ ◼ search-for-service (Search and browse the x402 bazaar marketplace for paid AP...)
│ ◻ send-usdc
│ ◻ trade
│ ◼ x402 (Search for new services and make paid API requests using ...)
```
Choose your LLM client and whether to install globally. For example:
```txt
│ ○ Augment (.augment/skills)
│ ○ IBM Bob (.bob/skills)
│ ❯ ● Claude Code (.claude/skills)
│ ○ OpenClaw (skills)
│ ○ CodeBuddy (.codebuddy/skills)
│ ○ Command Code (.commandcode/skills)
│ ○ Continue (.continue/skills)
│ ○ Cortex Code (.cortex/skills)
```
```txt
◆ Installation scope
│ ○ Project
│ ● Global (Install in home directory (available across all projects))
```
```txt
◆ Installation method
│ ● Symlink (Recommended) (Single source of truth, easy updates)
│ ○ Copy to all agents
```
And proceed with installation.
Read more about [skills](https://docs.cdp.coinbase.com/agentic-wallet/skills/overview).
After restarting your LLM client, you can set up your wallet and fund it _from within your LLM client_ using
```
/fund
```
Then, deploy a job via
```
/pay-for-service deploy.acu.run
```
:::note
Hitting `invalid_payload: contract call failed: execution reverted`? See [Troubleshooting](#troubleshooting).
:::
You are likely presented with a choice similar to
```
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
☐ Deploy target
What do you want to deploy (and pay for) at deploy.acu.run?
❯ 1. A script job
Deploy a script by IPFS URL (ipfs://Qm...). I'll need the IPFS URL and optionally a reward.
2. A VPS
Deploy an SSH-reachable Ubuntu VPS. I'll need at least an sshKey and maxExecutionTimeInMs.
3. Just price discovery
Only fetch/decode the 402 payment requirements. No payment, no deployment.
4. Type something.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
5. Chat about this
```
A typical flow to deploy a job on Acurast
```
❯ /pay-for-service
⏺ I'll check the wallet status first to make sure it's authenticated and ready for paid requests.
⏺ Bash(npx awal@2.0.3 status)
⎿ Wallet Server
✓ Running (PID: 33552)
… +4 lines (ctrl+o to expand)
⏺ The wallet is authenticated and ready.
To make a paid API request, I need to know which endpoint you want to call. Do you have a specific x402 service URL you'd like to use?
If not, I can help you search the bazaar marketplace for available paid services using the /search-for-service skill. What kind of service or API are you looking for?
❯ /pay-for-service deploy.acu.run
⏺ I see you want to use the deploy.acu.run service. Let me first check what endpoints and API documentation are available for this service.
⏺ Bash(npx awal@2.0.3 x402 pay https://deploy.acu.run --json)
⎿ {
"status": 200,
"statusText": "OK",
… +5 lines (ctrl+o to expand)
⏺ The root endpoint doesn't require payment. Let me check if there's documentation or an OpenAPI spec available.
⏺ Fetch(https://deploy.acu.run)
⎿ Received 4 bytes (200 OK)
⏺ Let me check if there's documentation in the current repository about this deploy agent service.
⏺ Read 1 file (ctrl+o to expand)
⏺ I found the documentation. The Acurast Deploy Agent at deploy.acu.run is an x402 service for deploying jobs to the Acurast decentralized cloud.
Service Details
┌──────────┬────────────────────────────────────────────┐
│ Field │ Value │
├──────────┼────────────────────────────────────────────┤
│ Endpoint │ https://deploy.acu.run/deploy │
├──────────┼────────────────────────────────────────────┤
│ Method │ POST │
├──────────┼────────────────────────────────────────────┤
│ Network │ Base (chain 8453) │
├──────────┼────────────────────────────────────────────┤
│ Payment │ USDC (dynamic pricing based on job reward) │
└──────────┴────────────────────────────────────────────┘
Required Parameters
┌────────┬────────┬────────────────────────────────────────────────────────┐
│ Field │ Type │ Description │
├────────┼────────┼────────────────────────────────────────────────────────┤
│ script │ string │ IPFS hash of the script (e.g., ipfs://Qm...) │
├────────┼────────┼────────────────────────────────────────────────────────┤
│ reward │ number │ Reward per execution in picoACU (1 ACU = 10¹² picoACU) │
└────────┴────────┴────────────────────────────────────────────────────────┘
Optional Parameters
- schedule - Job schedule (start/end times, interval, duration)
- slots - Number of processor slots (default: 1)
- runtime - NodeJS, NodeJSWithBundle, or Shell
- assignmentStrategy - Single or Competing
- allowOnlyVerifiedSources - Only verified processors (default: true)
---
Would you like me to deploy a job? If so, please provide:
1. IPFS script hash (e.g., ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2)
2. Reward amount in ACU (e.g., 0.0771 ACU)
❯ deploy ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2 with 0.0771 ACU reward
⏺ I'll deploy that script with 0.0771 ACU reward (77,100,000,000 picoACU).
⏺ Bash(npx awal@2.0.3 x402 pay https://deploy.acu.run/deploy -X POST -d '{"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2", "reward": 77100000000}'
--…)
⎿ {
"status": 200,
"statusText": "OK",
… +22 lines (ctrl+o to expand)
⏺ Deployment successful!
┌────────────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Field │ Value │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Job Hash │ 0xe8c81b116fb4412571c1edcd8764e35db4a96507b599c86baf51746c55aecc25 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Explorer │ https://hub.acurast.com/explorer/extrinsic/0xe8c81b116fb4412571c1edcd8764e35db4a96507b599c86baf51746c55aecc25 │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Payment TX │ 0x88053... on Base (chain 8453) │
├────────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Payer │ 0xceA0d23336f285f9d85d6b0c3DE7103eAEB168F9 │
└────────────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
Your job has been deployed to the Acurast decentralized cloud with a reward of 0.0771 ACU per execution.
```
## Programmatically deploy a job {#programmatically-deploy}
This approach uses Coinbase's `awal` tool directly, instead of via the LLM. This is a deterministic approach for dApps and backends.
1. **Set up a [Coinbase Agentic Wallet](https://docs.cdp.coinbase.com/agentic-wallet/quickstart)**
2. **Fund your agentic wallet**, then validate with:
```shell
npx awal balance
```
Example output:
```
Base
────────────────────────
USDC $20.21
ETH 0.0008
WETH 0.0008
```
You need both USDC (for the deploy) and a little ETH on Base (for gas) — see
[Troubleshooting](#troubleshooting) if a payment fails with insufficient funds.
3. OPTIONAL: Query accepted tokens and pricing before deploying
You don't need to calculate the cost yourself. We recommend omitting the **optional** `reward` — in which case the agent estimates a market rate.
For more control, you can query the deploy agent to see accepted tokens and the quoted price before making a payment:
```shell
curl "https://deploy.acu.run/deploy" -X POST -H "Content-Type: application/json" -i -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"allowedSources": null,
"allowOnlyVerifiedSources": true,
"schedule": {
"startTime": 1777593600000,
"endTime": 1777651200000,
"duration": 3600000,
"interval": 3600001,
"maxStartDelay": 10000
},
"memory": 0,
"networkRequests": 0,
"storage": 0,
"requiredModules": [],
"assignmentStrategy": "Single",
"slots": 1,
"reward": 77136800000,
"minReputation": 0,
"runtime": "NodeJS"
}' | sed -n 's/^payment-required: //Ip' | tr -d '\r' | base64 -d | jq
```
Example response:
```json
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "http://deploy.acu.run/deploy",
"description": "Deploy Acurast job",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:8453",
"amount": "79000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0x9b442C40DAaC631F74de4094F2998E4aC016098A",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USD Coin",
"version": "2"
}
}
],
"extensions": ...
}
```
4. **Deploy using the agentic wallet**
See [First App Deployment](/developers/deploy-first-app) to learn how to upload your script and receive an IPFS URL using the Acurast CLI.
```shell
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"reward": 77136800000
}'
```
The agent handles deployment to the Acurast network once an x402 [facilitator](https://docs.cdp.coinbase.com/x402/core-concepts/facilitator) confirms the payment was successful.
Example response:
```
Response:
{
"success": true,
"paymentTxHash": "x402-1776088198704",
"payer": "unknown",
"chainId": 8453,
"acurastJobHash": "0xfe16aa9be55907284cf7eafaabfc9b41e6d7caa19a55d7b518146551783a1b83",
"explorerLink": "https://hub.acurast.com/explorer/extrinsic/0xfe16aa9be55907284cf7eafaabfc9b41e6d7caa19a55d7b518146551783a1b83",
"message": "Job deployed successfully"
}
```
The deploy agent has deployed the job under [`5H3ChM1QgrYyGwzmyQ1QkdFCJxzakjWsbQN5tSGP8dkn2VCR`](https://hub.acurast.com/explorer/address/5H3ChM1QgrYyGwzmyQ1QkdFCJxzakjWsbQN5tSGP8dkn2VCR).
## Programmatically deploy a VPS (SSH tunnel) {#deploy-a-vps-ssh-tunnel}
Instead of a job spec, send `{ "vps": { "sshKey": "..." } }`. The deploy agent
generates a P-256 tunnel keypair, precomputes the `.acu.run` subdomain,
submits a Shell-runtime job to Acurast, and returns the shell connect command
**before the processor has even started**. Once a processor picks the job up
(~1-3 min), it installs Ubuntu + dropbear inside a proot sandbox, opens the
Acurast reverse tunnel targeting dropbear, and Let's Encrypt issues a real
certificate for the precomputed subdomain.
The domain is knowable off-chain because the tunnel's `clientId` is
`hex(sha256(compressed_pubkey)[0:8])` and the agent controls the keypair — it
injects the private key into the job as an encrypted env var after the match.
### Minimal deploy
`sshKey` and `maxExecutionTimeInMs` are the two required fields;
`maxExecutionTimeInMs` bounds how long the VPS runs. `reward` is **optional** —
omit it and the agent estimates a market rate for that window from the matcher
(the 402 quote reflects the estimate). Provide `reward` only to override it
(see [VPS parameters](#vps-parameters)).
As mentioned above, you can deploy [via your LLM](#use-via-llm) asking it for a VPS.
Instead you can programmatically deploy a VPS with
```shell
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d "{
\"vps\": {
\"sshKey\": \"$(cat ~/.ssh/id_rsa.pub)\",
\"maxExecutionTimeInMs\": 7200000
}
}"
```
Example response:
```json
{
"success": true,
"acurastJobHash": "0x...",
"domain": "https://.acu.run",
"probeUrl": "https://deploy.acu.run/vps/probe?domain=.acu.run",
"sshCommand": "ssh -o \"ProxyCommand=openssl s_client -quiet -servername %h -connect %h:443\" root@.acu.run",
"explorerLink": "https://hub.acurast.com/explorer/extrinsic/0x..."
}
```
### Connect
The tunnel wraps traffic in TLS (that's what gives the Let's Encrypt cert on
`.acu.run`), so SSH speaks through an `openssl s_client` `ProxyCommand`
that terminates the outer TLS at the relay. Use `-i` + `IdentitiesOnly=yes` to
force the exact key you submitted — otherwise a client with multiple keys in
`~/.ssh/` may present a non-matching one first and hit
`Permission denied (publickey)` before offering the right one.
**One-liner:**
```shell
ssh -o "ProxyCommand=openssl s_client -quiet -servername %h -connect %h:443" \
-i ~/.ssh/id_rsa -o IdentitiesOnly=yes \
root@.acu.run
```
**Ergonomic version** — add this once to `~/.ssh/config`:
```
Host *.acu.run
ProxyCommand openssl s_client -quiet -servername %h -connect %h:443
User root
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
StrictHostKeyChecking accept-new
```
Then forever after just:
```shell
ssh .acu.run
```
:::note Key-only auth
The tunnel bundle runs dropbear with `-s -g` (no password auth advertised).
If `SSH_AUTHORIZED_KEY` fails to reach the processor for any reason the job
aborts rather than exposing a password-authenticated shell.
:::
### Poll for readiness
The deploy response returns immediately, but the processor still needs
~2-5 min to install Ubuntu, dropbear, and open the reverse tunnel. Poll
`GET /vps/probe?domain=` until `ready: true`:
```shell
curl -s "https://deploy.acu.run/vps/probe?domain=.acu.run" | jq
```
```json
{
"domain": ".acu.run",
"ready": true,
"banner": "SSH-2.0-dropbear_2025.88",
"cert": {
"subject": ".acu.run",
"issuer": "YE2",
"notBefore": "...",
"notAfter": "..."
}
}
```
`ready: false` responses carry an `error` field (`timeout`, `unrecognized
banner`, `connection closed before banner`, TLS handshake failures) — retry
on an interval, don't hot-loop.
### Expose an HTTP service on the same subdomain
Add `httpPort` to the request; the bundle then installs
[sslh](https://github.com/yrutschle/sslh) in front of dropbear so the same
`.acu.run` domain serves both shell and HTTP simultaneously. Byte-0
protocol sniffing routes shell traffic to dropbear and HTTP requests to your
app on `127.0.0.1:`.
```shell
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d "{
\"vps\": {
\"sshKey\": \"$(cat ~/.ssh/id_rsa.pub)\",
\"reward\": 48686320000,
\"maxExecutionTimeInMs\": 7200000,
\"httpPort\": 8080
}
}"
```
Response echoes it back as `httpPort: 8080`. Inside the VPS, bind your service
on `127.0.0.1:8080` (or `0.0.0.0:8080`). Browsers hitting
`https://.acu.run/` then get your app; the existing `ProxyCommand`
recipe keeps working for shell access.
**Tradeoffs:**
- Small (~200 ms) connect-time delay on new shell connections while sslh
waits its `--on-timeout` fallback (imperceptible for interactive use).
- One HTTP backend per subdomain. For multiple services, run nginx / caddy
inside the VPS and reverse-proxy from there.
- Adds ~5-10 s to first-boot on the processor for `apt-get install sslh`.
### VPS parameters
| Field | Type | Description |
| --- | --- | --- |
| `sshKey` | string | **REQUIRED** OpenSSH public key line (e.g., `ssh-ed25519 AAAA... user@host`), installed into dropbear's `/root/.ssh/authorized_keys`. |
| `reward` | number | **OPTIONAL** Reward per execution in picoACU (1 ACU = 10¹² picoACU). Omit it and the agent estimates a market rate for the `maxExecutionTimeInMs` window. Provide it only to override (e.g. `48686320000` ≈ 0.049 ACU). |
| `maxExecutionTimeInMs` | number | **REQUIRED** How long the VPS runs before the processor tears it down, in ms (setup time counts). No default. Drives the reward estimate when `reward` is omitted. |
| `httpPort` | number | Enable HTTP multiplexing on the same subdomain via sslh. Must be an integer in `1024..65535`. Bind your app on `127.0.0.1:` inside the VPS. |
| `image` | string | Image preset. Currently only `"ubuntu"` (default) — Ubuntu 25.04 aarch64 proot-distro. |
| `network` | string | Target network: `"mainnet"` (default) or `"canary"`. |
| `callbackUrl` | string | Optional webhook receiving `log` / `started` / `error` JSON events from the tunnel bundle. Useful for observing boot progress or catching install failures. |
| `minMemory` | number | Minimum total processor RAM in bytes (matched against the `v1_ram_total` benchmark pool). |
| `minStorage` | number | Minimum available processor storage in bytes (matched against `v1_storage_avail`). |
| `minCpuScore` | number | Minimum single-core CPU benchmark score (Geekbench-style; matched against `v1_cpu_single_core`). |
| `minCpuMultiScore` | number | Minimum multi-core CPU benchmark score (matched against `v1_cpu_multi_core`). |
| `minProcessorReputation` | number | Minimum processor reputation on a `0..1_000_000` scale. Default `0` (no reputation filter). |
| `startAt` | object | When the execution window opens — relative `{ "msFromNow": 180000 }` or absolute `{ "timestamp": }`. Default `{ "msFromNow": 180000 }` (3 min). Must leave ≥ 2 min lead time for a processor to match. |
| `maxStartDelayMs` | number | Maximum tolerated start-delay in ms. Default `30000` (30 s). Widen for slower processors that may not be ready in time. |
## Full job specification
:::tip Generate job specification
The easiest way to craft a job specification as JSON is using the [Acurast CLI](/developers/tools/cli) with the extra argument `--only-upload` to just output the IPFS URL:
```shell
acurast deploy --only-upload
```
:::
You can provide a complete job specification for more control:
```shell
npx awal@latest x402 pay "https://deploy.acu.run/deploy" -X POST -d '{
"script": "ipfs://QmZ9mvN4RFCqSqivB2LF3VF1qgrDGTW393PJezbdPy7nH2",
"allowedSources": null,
"allowOnlyVerifiedSources": true,
"schedule": {
"startTime": 1777593600000,
"endTime": 1777651200000,
"duration": 3600000,
"interval": 3600001,
"maxStartDelay": 10000
},
"memory": 0,
"networkRequests": 0,
"storage": 0,
"requiredModules": [],
"assignmentStrategy": "Single",
"slots": 1,
"reward": 77136800000,
"minReputation": 0,
"runtime": "NodeJS"
}'
```
The `jobSpec` object follows the standard Acurast job registration format:
| Field | Type | Description |
| -------------------------- | ---------------- | ----------------------------------------------------- |
| `script` | string | **REQUIRED** IPFS URL of an already-pinned script (`ipfs://Qm...`). The agent does not upload scripts — pin first (e.g. `acurast deploy --only-upload`) and pass the URL. Non-`ipfs://` values are rejected with a `400`. |
| `allowedSources` | string[] \| null | Processor whitelist (null = any) |
| `allowOnlyVerifiedSources` | boolean | Require attested processors |
| `schedule.startTime` | number | Job start time (Unix ms) |
| `schedule.endTime` | number | Job end time (Unix ms) |
| `schedule.duration` | number | Execution duration (ms) |
| `schedule.interval` | number | Execution interval (ms) |
| `schedule.maxStartDelay` | number | Max acceptable start delay (ms) |
| `memory` | number | Max memory bytes (0 = default) |
| `networkRequests` | number | Max network requests (0 = default) |
| `storage` | number | Max storage bytes (0 = default) |
| `requiredModules` | number[] | Required modules (e.g., LLM) |
| `assignmentStrategy` | string | "Single" or "Competing" |
| `slots` | number | Number of processor slots |
| `reward` | number | Reward per execution (picoACU). Omit to let the agent estimate a market rate. |
| `minReputation` | number | Min processor reputation (0-1000000) |
| `runtime` | string | "NodeJS", "NodeJSWithBundle", or "Shell" |
## Troubleshooting
### `invalid_payload: contract call failed: execution reverted`
**Symptom:** A deploy payment fails with
`invalid_payload: contract call failed: execution reverted`. See
[coinbase/payments-mcp#30](https://github.com/coinbase/payments-mcp/issues/30).
**Workaround:** For now, creating a new wallet account under a different email
address might resolve the issue.
### `insufficient funds` even though USDC balance is enough
Payments settle on Base, so the wallet needs a small amount of **Base ETH** to
cover gas — having only USDC is not enough. Per
[Coinbase](https://docs.cdp.coinbase.com/agentic-wallet/welcome), keep a little
ETH on Base in the wallet alongside your USDC. Check with `npx awal balance` and
top up ETH if it reads `0`.
---
## DevTools
:::tip Which tool should I use?
- **[CLI](/developers/tools/cli)** — interactive local workflow.
- **[SDK](/developers/tools/sdk)** — programmatic deploys from TypeScript/JavaScript.
- **[Deploy Agent](/developers/tools/deploy-agent)** — pay with USDC on Base via x402.
- **DevTools** — web dashboard for live logs and debugging of existing deployments.
:::
## Introduction
Acurast DevTools is a web-based dashboard that shows live output from your processors, including `console.log`, `console.warn`, `console.error`, `console.info`, and `console.debug` messages.
This makes it easy to debug and monitor your deployments in real time.
## Setup
**1. Enable DevTools in your project configuration** (`acurast.json`):
```json
{
"projects": {
"my-project": {
"projectName": "my-project",
"fileUrl": "dist/bundle.js",
"enableDevtools": true
}
}
}
```
**2. Deploy your project:**
```bash
acurast deploy my-project
```
After deployment, the CLI prints a DevTools URL with a view key — open it in your browser to see your logs.
**3. Paste the view key** to open the DevTools dashboard:
**4. View your deployment details**, including status, specification, logs, and uploaded files:
**5. Inspect live logs** from your processors in the Log Viewer:
## Requesting a New View Key
View keys are time-limited. If yours has expired, request a new one:
```bash
acurast devtools
```
This prints a fresh DevTools URL that you can open in your browser.
## File Uploads
When DevTools are enabled, your processor scripts can upload files (up to 10 MB) to the DevTools API using the `_DEVTOOLS_` global.
### API
```typescript
_DEVTOOLS_.uploadFile(filename, content, mimeType, onSuccess, onError);
```
| Parameter | Type | Description |
| --- | --- | --- |
| `filename` | `string` | Name of the file to upload |
| `content` | `string` | File content |
| `mimeType` | `string` | MIME type (e.g., `"text/plain"`, `"application/json"`) |
| `onSuccess` | `(response) => void` | Callback on successful upload |
| `onError` | `(error) => void` | Callback on failure |
The success callback receives an object with the following fields:
```json
{
"id": "file-id",
"filename": "output.json",
"mimeType": "application/json",
"fileSize": 1024,
"createdAt": "2025-04-10T12:00:00Z"
}
```
### Example
```javascript
_DEVTOOLS_.uploadFile(
"result.json",
JSON.stringify({ status: "ok", data: [1, 2, 3] }),
"application/json",
(response) => {
console.log("Upload successful:", response.id);
},
(error) => {
console.error("Upload failed:", error);
}
);
```
:::note
File uploads only work when DevTools are enabled (`"enableDevtools": true`) and the deployment uses a local bundle (not an `ipfs://` URL). Using DevTools with an IPFS URL will show a warning, since the DevTools snippet can only be injected into local bundles.
:::
## Privacy
- Logs are only accessible with a valid view key.
- The key is scoped to the specific deployment.
- Only the deployment owner can request new keys.
## Environment Variables
You can customize the DevTools URLs using environment variables in your `.env` file:
| Variable | Default | Description |
| --- | --- | --- |
| `ACURAST_DEVTOOLS_URL` | `https://devtools.acurast.com` | DevTools frontend URL |
| `ACURAST_DEVTOOLS_API_URL` | `https://api.devtools.acurast.com` | DevTools API URL |
---
## Acurast SDK
:::tip Which tool should I use?
- **[CLI](/developers/tools/cli)** — interactive local workflow. Best for getting started and day-to-day deployments.
- **SDK** — programmatic deploys from your own TypeScript/JavaScript code. Best for CI, backends, and custom flows.
- **[Deploy Agent](/developers/tools/deploy-agent)** — pay with USDC on Base via x402. Best for AI agents and services without ACU accounts.
:::
## Introduction
`@acurast/sdk` is the programmatic entry point for developers who want to deploy and manage deployments on Acurast from TypeScript/JavaScript. It bundles everything needed to upload a project to IPFS, register a job on-chain, match it with processors, and manage deployments — including Node.js, Cargo, and [Shell](/developers/tools/cli#shell-runtime) runtimes.
[npm](https://www.npmjs.com/package/@acurast/sdk) · [GitHub](https://github.com/Acurast/acurast-cli) · [Examples](https://github.com/Acurast/acurast-cli/tree/main/examples)
## Installation
```bash
npm install @acurast/sdk
```
The SDK relies on `@polkadot/*` packages as peer dependencies:
```bash
npm install @polkadot/api @polkadot/api-augment @polkadot/keyring \
@polkadot/types @polkadot/types-codec @polkadot/util \
@polkadot/util-crypto @polkadot/wasm-crypto
```
## Which module do I need?
The SDK is split into subpath exports so you import only the parts you need.
| Import | Purpose |
| --- | --- |
| `@acurast/sdk/deploy` | High-level deployment: zip a project, upload to IPFS, register a job — in one call |
| `@acurast/sdk/chain` | Direct chain access: wallets, balances, job registration, assignments, env vars |
| `@acurast/sdk/ipfs` | Upload deployment scripts to IPFS |
| `@acurast/sdk/matcher` | Pricing and processor matching helpers |
| `@acurast/sdk/types` | Shared TypeScript types |
## `@acurast/sdk/deploy`
High-level deployment utilities. Zip a local project, upload it to IPFS, and register the job on the Acurast chain in a single call.
```typescript
const config = await loadAcurastConfig('./acurast.json')
await deployProject({ config /* ... */ })
```
Also exports `zipFolder`, `createManifest`, `checkIsFolder`, and a `Logger` interface with a `NOOP_LOGGER` default.
## `@acurast/sdk/chain`
Direct access to the Acurast chain: wallets, balances, job registration, assignments, environment variables, and app versions.
```typescript
walletFromMnemonic,
getBalance,
registerJob,
editScript,
transferEditor,
jobAssignments,
AcurastService,
setEnvVars
} from '@acurast/sdk/chain'
```
Helpers include `convertConfigToJob`, duration constants (`second`, `minute`, `hour`, `day`), sensible defaults (`DEFAULT_REWARD`, `DEFAULT_REPLICAS`, ...), the `JobEnvironmentService` for encrypted env vars, and an `InMemoryKeyStore`.
Mutable deployments support post-creation updates via `editScript` (swap the IPFS script CID) and `transferEditor` (hand off editor permissions to another address).
For the list of environment variables available at runtime on the processor, see [Node.js Runtime Environment](/developers/build/nodejs-runtime-environment).
## `@acurast/sdk/ipfs`
Upload deployment scripts to IPFS.
```typescript
const cid = await uploadScript({
/* IpfsUploadOptions */
})
```
## `@acurast/sdk/matcher`
Pricing and matching helpers for jobs: check whether a job has matching processors, analyze fees, and get pricing advice.
```typescript
checkMatch,
checkMatchWithReward,
getAveragePrice,
getPriceDistribution,
getProcessorCount,
suggestCostPerExecution,
getFeeAnalysis,
analyzePricing,
fetchPricingAdvice
} from '@acurast/sdk/matcher'
```
## `@acurast/sdk/types`
Shared TypeScript types used across the SDK.
## Examples
See the [`examples/`](https://github.com/Acurast/acurast-cli/tree/main/examples) folder for end-to-end usage.
---
## Token
# Acurast Token (ACU)
## Chains and token contracts
The Acurast Token (ACU) is the native utility token of the Acurast network, powering the decentralized compute economy. Acurast is multichain in nature and the token is available on the Acurast mainnet as well as on various other blockchains and ecosystems. Deployed on its native chain, it can be bridged to Ethereum using the Acurast HyperDrive bridge and further from Ethereum to various EVM chains via LayerZero bridges.
## Overview
ACU serves multiple critical functions within the Acurast ecosystem:
* **Deployment Fees**: Submitted by developers using Acurast Compute
* **Network Fees**: Required for transaction fees on the Acurast network
* **Rewards**: Provides economic security through Staked Compute as well as Base Benchmark Rewards
* **Collator rewards**: Rewards for validators and block producers
* **Cross chain transfers**: Bridged instances of the ACU token, to be moved across various networks
## Token instances
### ACU on Acurast Mainnet
ACU is the native currency of the Acurast Mainnet, which is Substrate-based Polkadot parachain. ACU is used to pay for chain interactions and is the currency to submit for deployments and that is distributed for Staking Rewards, Base Benchmark Rewards and Collator Rewards.
### ACU on Ethereum
On Ethereum, ACU is available as an ERC20 token, deployed at [`0x216b3643ff8b7BB30d8A48E9F1BD550126202AdD`](https://etherscan.io/token/0x216b3643ff8b7BB30d8A48E9F1BD550126202AdD)
Native Acurast tokens from Acurast Mainnet can be bridged to Ethereum and back, using the bridge at [hub.acurast.com](https://hub.acurast.com).
### ACU on Binance Smart Chain (bridged)
On Binance Smart Chain, ACU is deployed as a LayerZero OFT at [`0x6EF2FFB38D64aFE18ce782DA280b300e358CFeAF`](https://bscscan.com/token/0x6EF2FFB38D64aFE18ce782DA280b300e358CFeAF)
ERC20 ACU from Ethereum can be bridged to BSC and back, using the LayerZero bridge via [stargate.finance](https://stargate.finance/) or other LayerZero frontends.
### ACU on Base (bridged)
On Base, ACU is deployed as a LayerZero OFT at [`0xc5fEd7c8cCC75D8A72b601a66DffD7A489073F0b`](https://basescan.org/token/0xc5fEd7c8cCC75D8A72b601a66DffD7A489073F0b)
ERC20 ACU from Ethereum can be bridged to Base and back, using the LayerZero bridge via [stargate.finance](https://stargate.finance/) or other LayerZero frontends.
### ACU on Peaq (bridged)
On Peaq, ACU is deployed as a LayerZero OFT at [`0x165bcb970836F83c15b22c3c1622279d97A20446`](https://peaq.subscan.io/account/0x165bcb970836F83c15b22c3c1622279d97A20446)
ERC20 ACU from Ethereum can be bridged to Peaq and back, using the LayerZero bridge via [stargate.finance](https://stargate.finance/) or other LayerZero frontends.
## Bridging pathways and platforms
Bridging ACU from and to the native Acurast Mainnet, can be done from and to Ethereum, via the Acurast HyperDrive bridge.
ACU from Ethereum can then be bridged via [LayerZero](https://layerzero.network/) to and from various EVM chains. Bridging is also possible between all target chains, eg. from BSC to Base, or from Peaq to BSC. ACU will be available on commonly used LayerZero bridging interfaces, like [stargate.finance](https://stargate.finance/).
## Overview of deployed ACU token contracts and instances
:::warning CAUTION
Only token contracts listed on the official Acurast documentation are valid Acurast tokens. Beware of fake tokens sold for the purpose of deceiving users.
:::
| Chain | ChainID | Token Contract | Decimals | Symbol |
|---------------------|-------------------|--------------------------------------------|----------|--------|
| Acurast Mainnet | ParachainID: 3396 | native, no token contract | 12 | ACU |
| Ethereum | 1 (0x1) | [0x216b3643ff8b7BB30d8A48E9F1BD550126202AdD](https://etherscan.io/token/0x216b3643ff8b7BB30d8A48E9F1BD550126202AdD) | 12 | ACU |
| BSC | 56 (0x38) | [0x6EF2FFB38D64aFE18ce782DA280b300e358CFeAF](https://bscscan.com/token/0x6EF2FFB38D64aFE18ce782DA280b300e358CFeAF) | 12 | ACU |
| Base | 8453 (0x2105) | [0xc5fEd7c8cCC75D8A72b601a66DffD7A489073F0b](https://basescan.org/token/0xc5fEd7c8cCC75D8A72b601a66DffD7A489073F0b) | 12 | ACU |
| Peaq | 3338 (0xd0a) | [0x165bcb970836F83c15b22c3c1622279d97A20446](https://peaq.subscan.io/account/0x165bcb970836F83c15b22c3c1622279d97A20446) | 12 | ACU |
## ACU token on Coinlist
Since TGE, the ACU tokens for Coinlist sale participants are withdrawable to Ethereum.
## ACU token on various exchanges
Exchanges decide which instance of the Acurast token they offer. Carefully study which instance has to be deposited or can be withdrawn before transacting these tokens.
## Additional Resources
For more detailed information about the token economics and distribution, please visit the [Tokenomics](/discover/tokenomics) page.
---
## Discover Acurast
The numbers, mechanics, and audits behind the Real Decentralized Compute Network.
## Token & economics
- **[Acurast Token (ACU)](/discover/acurast-token)** — utility, supply, distribution.
- **[Tokenomics](/discover/tokenomics)** — incentive design, allocations, emission.
- **[Roadmap](/discover/roadmap)** — what's shipped, what's next.
## Traction
- **[Metrics](/discover/metrics)** — compute units onboarded, protected assets, transactions processed.
## Security & research
- **[Audits](/acurast-protocol/audits)** — third-party security reviews.
- **[Whitepapers](/acurast-protocol/whitepapers)** — academic and protocol papers.
- **[Protocol Architecture](/acurast-protocol/architecture/architecture)** — technical deep-dive.
## Related
- Holding ACU already? See **[Token Holders](/token-holders)** to claim, stake, vote.
- Want to build on Acurast? See **[Developers](/developers)**.
## FAQ
### Is there an Airdrop?
The Acurast Association has announced the Cloud Rebellion Airdrop with 10,000,000 ACU tokens to be distributed based on MIST☁️ collected across all seasons (24 months linear vesting).
[More details ↗](https://acurast.com/blog/announcements/the-acurast-association-is-announcing-the-cloud-rebellion-airdrop-to-reward-its-rebels/)
### What is the cACU to ACU conversion mechanism?
#### How it works
1. User initiates conversion on Acurast Canary
2. cACU are burned on Canary
3. ACU is assigned to the account on Mainnet with a 1 to 1 ratio.
e.g., 1000 cACU = 1000 ACU
4. On Mainnet the lock can be updated once between 3 to 48 cycles:
4.1. Default: Unlocked after 48x cycles at 28 days (3.7 years) = 1 to 1 ratio
4.2. Minimum: Unlocked after 3x cycles (2.8 months) = 6.5% ACU
4.3. Example: Unlocked after 24x cycles (1.75 years) = 50% ACU
#### Features
- The Conversion window is 90 days from its Activation afterwards no Conversion can be done.
- Converted and locked ACU can be Staked in Staked Compute Pool from Day 1 of Mainnet.
⟶ Staking Rewards are transferable (not locked) and claimable every epoch (1.5h)
- Converted and locked ACU can be used to participate in on-chain Governance
- If you continue to provide compute, you'll receive ACU rewards, that are transferable, from the Compute Pool.
---
## Traction & Metrics
# Acurast Traction
Live indicators of network adoption and security.
## Compute
- **around 300,000** compute units onboarded on incentivized testnet.
- **140+** countries with active Compute Providers.
- **250M+** transactions processed on Testnet.
## Security
- **$200M+** in digital assets protected across Bitcoin, Ethereum, Tezos, Polkadot, peaq, and others.
- TEE-backed verifiability on every Processor.
## Where to verify
- **[Acurast Hub](https://hub.acurast.com/)** — live network state.
- **[Audits](/acurast-protocol/audits)** — third-party security reviews.
> Numbers above are point-in-time snapshots. Check the [Acurast Hub](https://hub.acurast.com/) for current values.
---
## Roadmap
Key milestones achieved and upcoming:
- Genesis Mainnet Launch, TGE (Q1 2026). ✅
- Governance Activation for Decentralized Community Involvement. ✅
- Codename Cargo (Compute Containers) for modularized workloads, making serverless deployments even easier to deploy and scale.
- Codename Cray (Compute Clusters) cluster of hundreds of devices enabling high-performance compute tasks capable of running even the largest LLMs available. Effectively mitigating the vertical compute limitation arising from working with phones.
- Furthermore, the future roadmap includes:
- Codename Bazaar (Compute Economy), creating an active decentralized compute economy for developers to distribute entire software solutions seamlessly without limitations.
- Codename Rice (Compute Futures), innovating long-term decentralized compute economic strategies. Allowing compute providers to leverage their infrastructure and support their future scaling.
Acurast’s growth strategy is about activating a network effect at scale, where more phones bring more compute, more compute brings more builders, and more builders bring more value into the ecosystem. It’s a positive-sum loop powered by real-world demand, seamless integrations, and decentralized ownership, creating a compute marketplace flywheel.
Here’s how Acurast is scaling fast—and sustainably:
### 1. Mainnet Launch → Permissionless, Global Access
With the **Genesis Mainnet and TGE launched in Q1 2026**, Acurast is now fully open, decentralized, and permissionless. Anyone with a smartphone can become a compute provider. Anyone with workloads, from solo builders to AI startups, can deploy confidential applications on a decentralized network backed by hundreds of thousands of real, verifiable phones.
Mainnet is Acurast’s on-ramp to true global accessibility, where compute is no longer gatekept by geography, capital, or centralized infrastructure.
### 2. Seamless Native Integrations with Major Web3 Ecosystems
Acurast is being embedded directly into the world's most active blockchain ecosystems, **Solana, Ethereum, Polkadot, and beyond**, so web3 developers can access decentralized, confidential compute as easily as calling a smart contract.
These native integrations mean:
- Developers can spin up secure compute from within their dApp flows
- Protocols gain censorship-resistant backends
- Ecosystems unlock AI, automation, and zero-knowledge tasks without centralized dependencies
This makes Acurast not just complementary, but critical infrastructure for Web3’s next phase.
### 3. Expanding Beyond Web3: Serving SMEs and Enterprises
While crypto-native at its core, Acurast is **not limited to Web3**. **SMEs and enterprise use cases** are actively being supported — especially around **confidential AI, confidential compute, and secure edge deployments:**
- Allowing enterprises to tap into AI without the fear of exposing business-critical and proprietary data by tapping into confidential AI compute
- Workloads requiring confidential compute without the complexity or cost of standing up traditional cloud infrastructure
- Enterprises in emerging markets lacking access to reliable data centers
Acurast offers these users **a confidential, on-demand, scalable, and cost-efficient alternative** to centralized compute.
### 4. Hyper-Onboarding of Smartphones at Global Scale
The onboarding strategy is rooted in mutual value creation. Initiatives like the **Cloud Rebellion**, major partnerships, and region-specific programs are designed to scale from 70,000 devices to over 1 million, converting dormant phones into high-value, secure nodes.
Each new phone adds network supply, but also unlocks new regions and new use cases, especially in areas where traditional compute is inaccessible.
### 5. Unlocking New Economic Layers
With milestones like:
- **Cargo** (modular compute containers)
- **Cray** (clustered high-performance compute)
- **Bazaar** (open compute marketplace)
- and **Rice** (long-term compute futures)
This is not just scaling tech—it's building an open compute economy, where compute is programmable, tradable, and ownable. Each upgrade compounds utility, revenue, and token demand.
### 6. Community-Driven Governance & Protocol Expansion
The Acurast community shapes protocol decisions through **on-chain governance** and a self-replenishing treasury. This turns users into stakeholders, and stakeholders into contributors.
**\*In short:** Acurast's growth strategy creates a flywheel:
**More devices → more demand → more utility → more contributors → more value.**
And at the center of that flywheel is a single insight: **you don’t need a data center to be part of the future of compute—just your phone.**
Now is the time to join.
---
## Tokenomics
Acurast is the decentralized compute network designed to power the emerging decentralized compute economy by aligning developers, compute providers, and end-users around shared incentives.
At the heart of Acurast is the ACU token and economic model, fueling a secure, scalable, and decentralized compute ecosystem while incentivizing active collaboration and sustainable growth.
Also, with tokenomics, the focus is on a sustainable community-first approach.
## Key Highlights
- **Initial Supply:** 1,000,000,000 ACU
- **Fixed inflation:** 5% fixed annual inflation distributed to Staked Compute Pool (70%), Compute Pool (10%), On-chain Treasury (15%), Collators (5%)
- **Only 6.5% allocated to early backers** — while important for funding and supporting development all the way to mainnet, this allocation was kept very low in order to facilitate a fair launch of the token.
- **Nearly 70%** of tokens are allocated to the community or community-supporting purposes (Community Treasury, Community Activation, Operational Funds, Liquidity Provision).
- **Team and Advisors have a 6 month lock-up and 36 months linear vesting,** aligning them with the long-term mission of the project.
By capping Early Backers at just 6.5% and allocating the majority of tokens to the community and community-supporting initiatives, the token distribution **puts the ecosystem interests first,** acknowledges early contributions to the broader crypto ecosystem, and **fosters the creation of services and products** on top of Acurast — ultimately supporting the project's roadmap and long-term objectives. Moving the needle again to decentralization, because decentralized compute is not for the few but for the many.
## Token Utility
The ACU token is what makes the entire decentralization of the protocol possible. Decentralization is one of Acurast's core values and impacts every protocol-related decision. ACU enables users to participate and interact with the protocol with these key functions:
1. **Network Fees** As a very active orchestration layer (over 395M transactions on testnet) the Acurast network requires transaction fees to avoid spamming and to keep a high quality of service to this crucial component.
2. **Incentivization of compute provision:** Similar to how Bitcoin rewards miners for creating blocks, Acurast is incentivizing compute providers through fixed inflation.
3. **Staking:** The novel concept of Staked Compute allows Acurast to economically secure reliable, verifiable, and widely accessible compute resources through staking collateral. This enables participants, including those without hardware, to earn rewards, delegate stakes, and leverage restacking for enhanced capital efficiency and network governance.
4. **Governance:** With an on-chain treasury of over 24% allocation, which replenishes itself from part of the inflation. With token holders making the protocol and future development-related decisions.
## Genesis Token Allocation
### Allocation Categories
- **Community Activation:** Tokens from the participation of the CoinList Token Launch, canary token conversions, airdrops and additional broader activation of the community on TGE.
- **Community Treasury:** With decentralization in mind from the first day, a large part of the tokens are allocated towards the community treasury, allowing ACU holders to determine through on-chain governance the future development of the protocol and support significant contributions via governance proposals.
- **Operational Funds:** Strategic funds with the mandate to foster protocol acceleration and growth, governed by the Acurast Association council.
- **Liquidity Provision:** This allocation is used exclusively to ensure enough liquidity of the Acurast Token on centralized and decentralized exchanges.
- **Early Backers:** A small, diverse group providing initial funding to ensure the protocol reaches mainnet maturity without heavy institutional influence.
- **Team and Advisors:** Tokens for the core contributors of the protocol, encompassing employees, advisors, founders and future key team members, with a 6 months lock-up and 36 month vesting; this allocation is structured to align for long-term incentives.
### Token Allocations
| Category | Supply | Token Amount (ACU) | Available at TGE | Vesting Cliff (months) | Linear Vesting (months) |
| ------------------------------------------------------------------------ | ------ | ------------------ | ---------------- | ------------------------------------------------------------------------ | ----------------------- |
| Community Activation | 5% | 50,000,000 | 0% | 0 | 24 |
| Early Compute Providers(cACU to ACU Conversion) _(Community Activation)_ | 6.5% | 65,000,000 | 0% | 3 – 44 (user selectable one time; exit possible any time after 3 months) | 0 |
| Cloud Rebellion Airdrop _(Community Activation)_ | 1% | 10,000,000 | 0% | 0 | 24 |
| Listing Incentives _(Community Activation)_ | 5% | 50,000,000 | 100% | 0 | 0 |
| CoinList Token Launch _(Community Activation)_ | 6.50% | 65,000,000 | 100% | 0 | 0 |
| Operational Funds | 11.50% | 115,000,000 | 0% | 3 | 24 |
| Community Treasury | 24.00% | 240,000,000 | 0% | 3 | 24 |
| Team and Advisors | 24.00% | 240,000,000 | 0% | 6 | 36 |
| Liquidity Provision \* | 10% | 100,000,000 | 100% | 0 | 0 |
| Early Backers | 6.50% | 65,000,000 | 0% | 0 | 24 |
_\* No lockups but only 30% allocated at TGE. These tokens are exclusively used to foster liquidity long-term._
## Token Utility
**Network Fees:** The Acurast network, a Proof of Stake blockchain, acts as an orchestrator for the decentralized compute economy. To interact with the Acurast network, ACU is required for gas fees (_i.e.,_ transaction fees).
**Staking:** Processors and delegators stake ACU to participate in the [Staked Compute Pool](/token-holders/staking/overview), earning rewards from epoch inflation for providing and securing compute capacity. Every ACU holder can participate through Delegation without running their own Processor.
**Compute Costs:** When developers schedule deployments, compute costs are paid as gas fees.
**Governance:** Holders of ACU can engage in protocol governance by voting on a variety of proposals brought forward by the community, guiding the development of the protocol and its components, and ensuring true decentralization and future-proof evolution by design.
## Inflation
Inflation is what makes the Acurast process sustainable by rewarding active participation in the protocol and creating incentives to be run as a decentralized protocol indefinitely. The protocol sets a fixed inflation of 5% annually, depending on various on-chain metrics, and can be further adapted through governance votes.
- 70% → Staked Compute Pool (The rewards for Staking). More on [**Staked Compute ↗**](/token-holders/staking/overview)
- 15% → Treasury
- 10% → Benchmark rewards (Base rewards processors, shared in relation to the benchmarks, independent from Staking). More on [**Benchmarks ↗**](/processors/benchmarks)
- 5% → Acurast blockchain block producers (Collators aka Validators)
---
## FAQ
## Overall
### What is Acurast
Acurast is redefining compute by utilizing billions of smartphones – no data centers required. This verifiable, scalable, and confidential compute network enables users to run secure applications on decentralized infrastructure at scale—without compromising speed or privacy.
Acurast has already onboarded around 300,000 compute units worldwide on its incentivized testnet making it the most decentralized verifiable compute network available today. This impressive amount of compute already powers mission critical workloads with high-security and AI requirements.
This isn’t just another DePIN protocol — it’s a game-changer that’s redefining how the world computes.
### How can you provide compute with your phone on Acurast?
Get started as a [Processor](/processors/acurast-processors) on Acurast and provide compute to receive rewards, either with Lite or Core:
#### **Acurast Processor Lite**
Lite is more flexible, can be installed on your everyday phone and activated to provide compute when it’s feasible for you. [How to onboard ↗](https://youtu.be/2Pxw2u0pZ_E?si=jHPCk82QI5I7rOKA)
Install the Acurast Processor Lite application on Android or iOS to get started.
[**Google Play ↗**](https://play.google.com/store/apps/details?id=com.acurast.attested.executor.sbs.canary)
[**Apple App Store ↗**](https://apps.apple.com/kh/app/acurast-processor/id6517361921)
#### **Acurast Processor Core**
Core is meant for dedicated phones with the only purpose of providing compute to Acurast, completely locked down. [How to onboard ↗](https://www.youtube.com/watch?v=uLpBRUnmiPY)
**[Acurast Hub ↗](https://hub.acurast.com/)**
### Why is Acurast Using Mobile Phones?
When it comes to compute, phones are powerful and underestimated. These devices stand their ground in comparison with the server hardware of data centers. Still, they are more affordable in terms of acquisition cost and especially in terms of running cost due to their lower energy consumption. With your contribution, you can make them the perfect contender to disrupt the entire Compute industry.
### What are the use-case of Acurast?
Acurast enables decentralized, confidential, and scalable compute for various applications, including secure AI execution, decentralized bandwidth VPN services, automated on-chain trading strategies, and high-performance distributed computing for cost-efficient clusters, resilient website hosting, scalable zkProof generation and others.
### What wallets can I use on Acurast?
**[See the full list of supported wallets ↗](/token-holders/wallets/wallet-overview)**
### What is the difference between MIST, cACU & ACU?
- MIST are points, not tokens, used to incentivize rebels via the Cloud Rebellion.
- cACU is the Acurast Canary (incentivized testnet) token.
- ACU is the native token of the Acurast Mainnet.
### How can I fund my wallet?
Acurast tokens ACU, are available on various markets, see directories like [CoinMarketCap ↗](https://coinmarketcap.com/currencies/acurast/#Markets) or [CoinGecko ↗](https://www.coingecko.com/en/coins/acurast) for a list of markets.
Depending on the type of token you get (EVM or Acurast native), you might need to bridge it first, before you can send it to the wallet you use for Acurast Mainnet. See token section: [Acurast Token](/discover/acurast-token).
### Why is my balance decreasing sometimes?
Your balance may decrease slightly due to transaction fees when your Processor interacts with the Acurast network. Whenever the Processor reports to the chain (heartbeats, deployment acknowledgements, execution reports), minimal gas fees have to be paid, which can result in a decreasing balance. These fees are typically minimal, usually around 0.01 ACU per transaction.
### What is the cACU to ACU conversion mechanism?
#### How it works
1. User initiates conversion on Acurast Canary
2. cACU are burned on Canary
3. ACU is assigned to the account on Mainnet with a 1 to 1 ratio.
e.g., 1000 cACU = 1000 ACU
4. On Mainnet the lock can be updated once between 3 to 48 cycles:
4.1. Default: Unlocked after 48x cycles at 28 days (3.7 years) = 1 to 1 ratio
4.2. Minimum: Unlocked after 3x cycles (2.8 months) = 6.5% ACU
4.3. Example: Unlocked after 24x cycles (1.75 years) = 50% ACU
#### Features
- The Conversion window is 90 days from its Activation afterwards no Conversion can be done.
- Converted and locked ACU can be Staked in Staked Compute Pool from Day 1 of Mainnet.
⟶ Staking Rewards are transferable (not locked) and claimable every epoch (1.5h)
- Converted and locked ACU can be used to participate in on-chain Governance
- If you continue to provide compute, you'll receive ACU rewards, that are transferable, from the Compute Pool.
### Is there an Airdrop?
The Acurast Association has announced the Cloud Rebellion Airdrop with 10,000,000 ACU tokens to be distributed based on MIST☁️ collected across all seasons (24 months linear vesting).
[More details ↗](https://acurast.com/blog/announcements/the-acurast-association-is-announcing-the-cloud-rebellion-airdrop-to-reward-its-rebels/)
## Acurast Processor
### What is the difference between Acurast Processor Core and Lite?
Acurast Processor Core is meant for dedicated phones, completely locked down, and intended only to provide compute to Acurast.
Whereas Lite is more flexible, can be installed on your everyday phone and activated to provide compute when it's feasible for you.
If you have a phone that you don't need any more for anything else, go with Core.
**[Learn more about Acurast Processors ↗](/processors/acurast-processors)**
### What Android and iOS version are required?
The Android version required is Android 12 or higher, while the iPhone must be a 6s or later.
**[Check the list of recommended phones ↗](https://docs.google.com/spreadsheets/d/1ZvzmMVey4CM2tuif_zJfWiIxH1qkgA-l7BNJMw4vh54/edit?gid=1844886586#gid=1844886586)**
### Is there a difference in rewards between Acurast Processor Core and Lite?
As long as your phone is connected to the internet, and the Processor application app is running, you get a reward. You can increase the rewards by staking your ACU tokens, see [Staked Compute](/token-holders/staking/overview).
Typically, Acurast Core scores 15-20% better in the benchmark tests, which will result in higher Base Benchmark Rewards and more Compute that can be committed in Staking.
Read more about the [Rewards here](/processors/rewards).
### I’m already running other Processors. Can I manage them all with one account?
Yes, most likely, you’ve connected a wallet to the Acurast Hub; follow these steps:
- Go to the Acurast Hub and select “Add New Device” to create a new QR code
- Install Acurast Processor Lite from Google Play or the Apple App Store on the phone
- Open Lite and select “Connect to Hub”
- Scan the QR code
- Complete the setup process, Lite is now connected to the Hub
### How can I tell how many heartbeats a processor has had?
The device heartbeats every 30 minutes. Scanning the blockchain allows you to look for all heartbeats, but that would take some time. To track the recent performance of your Processors, you can use the [Acurast Monitoring Bot](https://t.me/AcurastBot) on Telegram.
### Do I still get rewards if my Processor is missing heartbeats?
**[Benchmark rewards](/processors/rewards)** are distributed if one of 3 heartbeats of an epoch (roughly 90 minutes) was received on chain. This means your device can miss some heartbeats without missing out on rewards.
### Do I need to factory reset my phone?
You only need to factory reset your phone if you are onboarding a Core device. However, for Processor Lite, you can use your everyday device without factory resetting it.
Learn more about the differences between **[Core and Lite ↗](/processors/acurast-processors)**
For step-by-step instructions on getting started, see the **[Compute Provider onboarding guide ↗](/processors/become-compute-provider)**.
## Lite
### How do I install Acurast Processor Lite?
Acurast Lite can be installed within minutes. The Acurast Processor Lite application can run on Android or iOS.
**[Follow the step-by-step installation guide ↗](/processors/become-compute-provider#for-processor-lite)**
### Android: Why do I need to setup an "Android Work" profile?
Android Work profiles are a built-in feature of Android, typically used by organizations to separate work apps and data from personal apps on the same device. This creates a secure, isolated container managed by an enterprise or organization.
The Acurast Processor Lite leverages this proven Android feature to create a secure, isolated environment for providing compute. The Work profile acts as a second profile completely independent from your private profile. By keeping your private profile separate from the one that provides compute, you'll get the best security and have no impact on your privacy.
### Android: Who manages the "Android work" profile?
You do. You're in complete control. Your device is connected to the decentralized Acurast protocol (Acurast Canary) and controlled by you. In no way do any individuals have access to your phone to install other applications in that work profile or make changes to it.
**Important Note:** During setup, you may see standard Android messages like "Your device isn't private" or "Your IT admin may see your data...". These are default warnings that appear whenever a work profile is created. In the case of Acurast, you remain in complete control - these are simply standard messages Android displays for all work profile setups.
### Android: How do I uninstall Acurast Processor Lite?
Because Lite uses the Android work profile, you’ll have to remove it completely to uninstall the app from your device.
If you have set up Lite with "Get Started" without a connection to your Acurast Hub, make sure that you backup your secret before removing the app; otherwise, you will lose access to your cACU.
Uninstalling Processor Lite:
1. Open the "Settings" with the cog on the top right on the Acurast Processor application (make sure you can see the compute status)
1. Click "Uninstall" and "Continue"
1. The Processor app in the work profile has been removed, you can now uninstall the lite app from your private profile and the work profile should be removed too (might be subject to Android version).
## Core
### How do I install Acurast Processor Core?
Acurast Processor Core is designed for dedicated Android phones. Follow the detailed installation instructions to get started.
**[Follow the step-by-step installation guide ↗](/processors/become-compute-provider#for-processor-core)**
### How to add Acurast Processor Core with NFC?
A short video by a community member onboard a device with a broken screen and an NFC.
**[NFC Onboarding ↗](https://youtu.be/lbAYV1kmV6o?si=_IbU5RfXYdlp5p-w)**
## Acurast Hub
### The "Add New Device" QR code is not showing?
When this happens, you can do two things:
- Disconnect and connect your wallet to the Hub.
- Use another browser.
---
## Post Mortem - 2025-11-04 - Acurast Canary chain stalled
## Post Mortem
### Date & Time
11.04.2025 at 2:30 GMT+2
### Engineer
Andreas Gassmann, Mike Godenzi
### Summary
The Acurast Canary chain stopped regularly producing new blocks for about 40mins, afterwards block production resumed but blocks were not finalized, after 2 hours blocks were finalized again.
### Status
Resolved
### Root causes
The Acurast collators could not produce any new blocks because the Kusama relay chain nodes they were connected to were in a crash loop, exhibiting the behavior described in [this Github issue](https://github.com/paritytech/polkadot-sdk/issues/4934).
The Kusama nodes were running on a version containing the bug detailed in the issue linked above. Restarting the nodes did not improve the situation.
After around 2 hours the Kusama relay nodes started to work properly again.
### Trigger
Kusama relay chain nodes used by the Acurast collators entered in a crash loop because of a bug described in [this Github issue](https://github.com/paritytech/polkadot-sdk/issues/4934).
### Resolution
The relay chain nodes affected started to work again after around 2 hours. Afterwards they have been all updated to the latest version.
In addition, all Acurast collators node have been configured with additional relay chain nodes as fallback.
### Timeline
2:30:
- block production halts
2:42:
- initial triage of issue
- discovered Kusama relay chain nodes used by the Acurast collators entered in a crash loop
- restarting of Kusama relay chain nodes
- restarting of all acurast parachain nodes
- blocks started to be produced but they were not getting finalized
- continues triage of issue after chain still did not recover
4:33:
- Kusama relay chain nodes started to work again
### Lesson learned
- Keep Kusama nodes up to date
- Have more additional backup nodes configured as the relay chain nodes for the Acurast Collators.
---
## Post Mortem - 2026-07-20 - Acurast Compute — Double claiming of staking rewards
# Post mortem: Acurast Compute — Double claiming of staking rewards
## Duplicate reward payout in `delegate_more` / `redelegate` / `kick_out`
#### Date: 2026-07-20
#### Authors: Simon
#### Status: FIXED
### Summary:
In `pallet-acurast-compute`, several staking flows paid an accrued delegation reward **twice**.
The internal helper `end_delegation_for` (via `withdraw_delegation_for`) already transferred the
delegator's accrued reward from the distribution account to the delegator, and then **returned that
same amount as a plain `Balance`**. Three callers — `delegate_more_for`, `redelegate_for`, and the
`kick_out` path — treated the returned value as "still owed" and transferred it a **second** time
from the distribution account. A delegator who ended/topped-up a delegation therefore received
double their accrued reward, at the expense of the shared compute distribution (staking pot)
account.
### Detection:
Reported by a user who found the issue, while reviewing the compute pallet's
reward-accounting code paths (the delegation lifecycle `delegate` → `delegate_more` / `redelegate` /
`kick_out` / `withdraw_delegation`). The ambiguity of a function that both performs a transfer **and**
returns the transferred amount for event emmission made the duplicate payout possible.
### When it was introduced:
The double payout was introduced in commit `0775beed` "feat: RedelegationBlockingPeriod"
(2025-10-30), a large staking refactor that moved the reward transfer **into**
`withdraw_delegation_for` / `end_delegation_for` but added a **redundant second transfer** in the
`delegate_more_for` and `redelegate_for` callers. The same duplicate-transfer pattern was then copied
into the new kick-out helper in `06168e9b` "feat: impl kick_out" (same day, 2025-10-30). The bug was
therefore live from 2025-10-30 until the fix on 2026-07-20 (~8.5 months). Before `0775beed`,
`delegate_more_for` called `end_delegation_for` and discarded its result, so no duplicate occurred.
### Root causes:
1. **Primary cause — type ambiguity between "already paid" and "still owed":**
Reward-producing functions returned a plain, `Copy` `BalanceFor` while also performing the
transfer internally. Nothing in the type distinguished a value that had already been paid from a
value the caller still had to pay, so callers re-paid it:
```rust
// withdraw_delegation_for (pre-fix): transfers AND returns the amount
let reward = Self::withdraw_delegator_accrued(who, commitment_id)?;
T::Currency::transfer(&distribution_account, who, reward, KeepAlive)?; // paid here
Ok(reward) // ...and returned
// delegate_more_for / redelegate_for / kick_out (pre-fix): pay it again
let reward = Self::end_delegation_for(who, commitment_id, false, false)?;
T::Currency::transfer(&distribution_account, who, reward, KeepAlive)?; // DUPLICATE payout
```
2. **Contributing cause — side effects buried in a helper:** the transfer happened deep inside
`withdraw_delegation_for`/`end_delegation_for`, so at the call site it was not obvious the tokens
had already moved.
3. **Contributing cause — no regression test** covered the equivalence of the reward paid across the
different exit paths (`withdraw_delegation` vs `delegate_more` vs `redelegate` vs `kick_out`).
### Impact:
- **Nature:** Duplicate payout of accrued delegation rewards from the compute distribution (staking
pot) account to the delegator.
- **Trigger:** Any delegator calling `delegate_more`, `redelegate`, or being `kick_out`-ed with a
non-zero accrued reward received 2× that reward. `withdraw_delegation` / `withdraw_commitment`
(the "plain claim" paths) were **not** affected.
- **Exploitability:** Reachable by any delegator via ordinary, permissionless extrinsics; no special
privileges required.
- **Bounded to once per epoch (key limiting factor):** the duplicated amount is the delegator's
`accrued_reward`, and `withdraw_delegator_accrued` **zeroes** `accrued_reward` as part of the claim.
A delegation's `accrued_reward` only grows when the pool's `reward_per_weight` increases, which
happens **exclusively at epoch boundaries** (`on_initialize` → `advance_epoch` → inflation
`distribute`). The abusing calls (`delegate_more` / `redelegate` / `kick_out`) end the delegation —
claiming and zeroing the accrued reward while paying it twice — and re-delegate fresh with
`reward_debt` reset to the current accumulator. Immediately repeating the call therefore finds
`accrued_reward ≈ 0` and doubles nothing. To extract another duplicate the attacker must wait for
the next epoch's distribution to accrue new rewards. Consequently the **extra (fraudulent) payout
is capped at roughly one epoch's worth of the delegator's legitimate reward, and can be repeated at
most once per epoch** — it is not an unbounded, repeat-in-a-single-block drain, which materially
limited the maximum extractable value and the rate of pot depletion.
- **Realized loss:** Material. An on-chain reconstruction (see below) puts the total wrongly-received
reward at **≈ 571,875 ACU** across 421 addresses. An earlier manual review had found "no large
scale abuse"; that conclusion was **wrong** — it missed that the `redelegate` path carried by far
the largest accrued rewards.
### Realized loss (measured on-chain):
Reconstructed from the mainnet indexer + the fact that each affected call paid the reward via **two
equal `Transfer`s from the compute distribution account** (`PalletId("cmptepid")`,
`0x6d6f646c636d707465706964…`) to the recipient within one extrinsic. For every event of the five
vulnerable paths we take the recipient's `cmptepid` transfers in that exact extrinsic; the wrongly
received amount is `total_paid − largest_single_payment` (0 for a correct single-transfer call).
This is an upper bound. Tool: `scripts/dm-loss`. Measured 2026-07-23; detected
double-claims span 2026-01-20 → 2026-07-21 (1,840 events).
**By path:**
| Path | Duplicate loss | Notes |
|------|---------------:|-------|
| `redelegate` | 522,960 ACU | dominant — largest accrued rewards |
| `delegate_more` | 48,916 ACU | |
| `kick_out` | 0 ACU | extrinsic never called on mainnet |
| `compound_delegation` | 0 ACU | never double-paid¹ |
| `compound_stake` | 0 ACU | never double-paid¹ |
| **Total** | **≈ 571,875 ACU** | 421 addresses, 1,840 double-claims |
¹ The `compound_*` paths claim via `withdraw_delegation_for` **first**, which zeroes
`accrued_reward`; the subsequent internal `delegate_more_for` / `stake_more_for` then re-pays ≈ 0.
Verified on-chain: across ~10,800 compound anchors, none ever produced two transfers. The bug only
realizes when the double-paying helper is the first thing to touch the accrued reward — i.e. the
explicit `delegate_more` / `redelegate` / `kick_out` extrinsics.
**Top 10 recipients (98.6% of the total; the remaining 411 addresses account for ≈ 8,093 ACU):**
| # | Address (SS58) | Wrongly received | Events | Path(s) |
|---|---------|-----------------:|-------:|---------|
| | **From here are all known vesters**.
| 1 | `5G1hb3aG4EvqshKAgZy5qTQHgVZZUEEgRvHczZ8zUjgXzfKK` | 212,687.83 ACU | 1 | redelegate |
| 2 | `5ChwtgueM1qg3HEM59B8kLT7d5ipyJUgof2i46SohB1Z4rWi` | 174,623.16 ACU | 1 | redelegate |
| 3 | `5Dcff2aGrPY3E99F5H66MyjfGnVEZe3VM8J5ePX92yA8ud8M` | 114,899.68 ACU | 5 | delegate_more + redelegate |
| 4 | `5DhN5RKJiCGmkburNX7nuar8CUUiXYXmhoNvDrrgF1UqfmRJ` | 42,174.40 ACU | 1 | delegate_more |
| | **Until here all known vesters**.
| 5 | `5EYydM9EA7RbXNrB6mP5WHg3XDobZQ7gJMgdJ14QAhPjT8cN` | 8,849.35 ACU | 1 | redelegate |
| 6 | `5CAjErHxCfr9n6RTEbzffX83g4ft1KDc8axBizm5goTeKYc2` | 3,445.28 ACU | 1 | redelegate |
| 7 | `5EfeQTU8WYLmJ6c44FSXsCR53CCi2mN3yMXGErmErLnjEiqe` | 3,162.84 ACU | 2 | delegate_more + redelegate |
| 8 | `5EtNFo1uTGqvnVJVrCqXmCwKSwyC8mfwzUj6gha7TQaSZnhk` | 1,561.28 ACU | 1 | redelegate |
| 9 | `5CRYfosnAHVik5SbzkpXvqpGVKtK2j16e8NQS2qPu7pbEFjN` | 1,215.77 ACU | 19 | delegate_more + redelegate |
| 10 | `5GjiL6UP1spfTtaoiNRBfEQ8m7ibxXnAndN8usj2HrLEDNCN` | 1,162.22 ACU | 4 | delegate_more + redelegate |
The top two entries are single `redelegate` calls (212,688 + 174,623 ACU), and the top three
recipients alone account for ≈ 89% of the loss — consistent with a few very large stakers whose
per-epoch accrued reward was doubled, rather than broad small-scale abuse.
### Theoretical worst case:
**Model.** The extra payout per abusing call equals the delegator's `accrued_reward`, which
`withdraw_delegator_accrued` **zeroes** on claim and which only regrows at epoch boundaries (see
*Impact → Bounded to once per epoch*). So a single delegator can double at most **one epoch's worth
of their reward, once per epoch**. The maximum possible over the whole live window is therefore the
case where **every delegator routes every epoch's reward through a doubling path** — i.e. the extra
(fraudulent) payout ceiling equals **the total delegation rewards distributed to delegators over the
bug's live window**. (Committer/`stake_more` rewards are excluded: that path was not vulnerable and
double-paid 0 on-chain.)
**Computation.** Summing every realized delegator reward over the window — the amount-bearing claim
events plus the legit reward of each `delegate_more`/`redelegate` (whose events carry no amount):
| Reward stream | Amount | Events |
|---------------|-------:|-------:|
| `DelegatorWithdrew` (plain claim, safe path) | 4,594,529 ACU | 25,684 |
| `delegate_more` + `redelegate` (legit portion) | 571,877 ACU | — |
| `DelegatorCompounded` | 10,098 ACU | 2,514 |
| `DelegationEnded` | 236 ACU | 538 |
| `KickedOut` | 0 ACU | 0 |
| **Total delegator rewards distributed** | **≈ 5,176,740 ACU** | |
**Theoretical worst-case extra payout ≈ 5.18M ACU.** The bulk (4.59M) is rewards that were actually
claimed via the *safe* `withdraw_delegation` path — tokens that **would** have been doubled had those
delegators instead claimed through `delegate_more`/`redelegate`. Against this ceiling, the
**realized loss of ≈ 571,875 ACU is ≈ 11%** of the maximum: roughly one-ninth of all delegator
rewards in the window were actually run through a doubling path.
*Caveats.* This is the mechanism ceiling and ignores pot liquidity — draining the full 5.18M would
require the distribution pot to hold ~2× the delegator reward each epoch (it is refilled by
inflation per epoch, so the true constraint is per-epoch, not cumulative). It also excludes rewards
that accrued but were never claimed (a small tail still legitimately claimable), and assumes stakes
large enough to have been worth exploiting. Reproduced by `scripts/dm-loss`.
### Timeline:
(all times UTC+01:00, from commit history)
| Time | Event |
|------|-------|
| 2025-10-30 | Double payout introduced: staking refactor moves the transfer into `withdraw_delegation_for`/`end_delegation_for` but adds a redundant second transfer in `delegate_more_for`/`redelegate_for` (`0775beed`) |
| 2025-10-30 | Same duplicate-transfer pattern copied into the new kick-out helper (`06168e9b`) |
| 2026-07-20 | Reported by a user |
| 2026-07-20 16:52 | Fix committed: redundant second transfers removed from `delegate_more_for` / `redelegate_for` / kick-out path; regression test `test_delegate_more_does_not_double_transfer_reward` added (`a67a8f2b`) |
| 2026-07-22 07:57 | Type-level hardening: rewards now flow through a move-only `Credit` (`RewardCreditOf`) that must be consumed exactly once (`969b30d5`) |
## Lessons Learned
### What went wrong
- A single value (`Balance`) was overloaded to mean two different things — tokens already moved vs.
tokens to move — and the compiler could not catch the resulting double spend.
- Token-moving side effects were hidden inside helpers whose return value looked like a pure
computation.
### What went well
### Where we got lucky
- The bug was caught by honest user.
- The exploit was time and real-reward bound.
- Only the top-up / exit paths were affected; the most common claim path (`withdraw_delegation`) was
correct, limiting the blast radius.
### Conclusion
**Actions taken:**
- Removed the duplicate transfers and added a regression test (`a67a8f2b`).
- Hardened the reward path at the **type system level** (`969b30d5`): reward-producing functions now
return a move-only `Credit` (`RewardCreditOf`) instead of transferring internally and returning a
`Balance`. A `Credit` is not `Copy`/`Clone`, is consumed exactly once via a single `payout` helper
(`resolve`), and is burned on drop — so paying a reward twice is now a **compile error**. Developer/LLM guidelines have been updated to "prevent double transfers with `Imbalance`/`Credit`" mechanics.
**Follow-ups:**
- Note that upon event-shape change: reward payouts now emit `Balances::Withdraw` + `Balances::Deposit`
instead of a single `Balances::Transfer`; downstream indexers (e.g. acurast-explorer) must be
updated accordingly.
---
## Post Mortem - 2026-07-20 - Acurast Token Conversion — replay of conversion messages
# Post mortem: Acurast Token Conversion — replay of conversion messages
## `Enabled` guard intentionally left out on retry and inbound-message paths (bridge could still mint locked funds while disabled) but deduplication state cleared on eventual user unlock
#### Date: 2026-07-20
#### Authors: Simon
#### Status: FIXED (bridge also deactivated as containment)
### Summary:
`pallet-token-conversion` migrates the native token from the source ("canary") chain to an Acurast
chain: a user burns balance on the source side, a cross-chain message is delivered, and on the
receiving side the pallet funds the target account from the pallet account and places a vesting hold
that the user later `unlock`s. The pallet has a governance master switch, `Enabled`, toggled via
`set_enabled` — the intended emergency lever to stop the bridge.
The core issue is that the pallet's inbound replay protection is only a transient, per-account check
(`LockedConversion` existence) that is **cleared when the user eventually `unlock`s**, and it was
designed without accounting for the **weak delivery guarantees of the underlying message protocol**:
IBC provides at-least-once, not exactly-once, delivery (a message with the same nonce can be re-sent
after its TTL). A conversion message replayed after the user has unlocked is therefore no longer
deduplicated and re-runs `fund` + `hold`, re-minting the "migrated" funds from the pallet account.
This was compounded by the `Enabled` kill switch being **intentionally** limited to `convert` — the
retry and inbound `process_conversion` paths were deliberately left runnable so in-flight conversions
could complete, and `unlock` deliberately stays open — so setting `Enabled = false` could not act as
an emergency backstop against the replay.
### Detection:
Reported by a user who found the issue, while reviewing the cross-chain bridge
stack (hyperdrive / IBC / token-conversion) — specifically which state-changing entry points honored
the `Enabled` kill switch and how inbound conversion messages are deduplicated.
### Root causes:
1. **Primary cause — replay protection is transient and cleared on unlock.** The only inbound dedup
is the `LockedConversion[account]` existence check (a duplicate is silently ignored *while a lock
exists*), keyed by account rather than by a durable message id/nonce. That state is **removed once
the user `unlock`s** their migrated funds. The design also **overlooked that the underlying message
protocol only guarantees at-least-once (not exactly-once) delivery** — by design, a message with
the same nonce can be legitimately re-sent after its TTL. As a result, a conversion message
**replayed after the user has unlocked** is no longer recognized as a duplicate and re-runs
`fund` + `hold`, re-minting the migrated funds.
2. **Contributing cause — the kill switch was intentionally limited, so it could not backstop the
replay.** By design, `ensure_enabled()` guarded only `convert`, and `unlock` is deliberately kept
callable even when disabled (so users can always release already-migrated funds); the retry
extrinsics and the inbound `process_conversion` path were intentionally left runnable so in-flight
conversions could still complete. The consequence was that `Enabled = false` did not halt the
fund-creating path and therefore could not serve as an emergency backstop against the replay
above. The fix adds the guard anyway, so disabling now stops every path except `unlock`:
```rust
fn ensure_enabled() -> Result<(), Error> {
if !Self::enabled() { return Err(Error::::NotEnabled); }
Ok(())
}
```
3. **Contributing cause — the fund-creating path is origin-less.** `process_conversion` is reached
from `MessageProcessor::process` (a decoded inbound message), not from a signed extrinsic, so the
replay can be triggered purely by message delivery, without any attacker-controlled account action.
### Impact:
- **Nature:** The primary emergency control (`set_enabled(false)`) did not halt the fund-creating
path. While disabled, an inbound or replayed conversion message could still mint locked funds from
the pallet account to the target account.
- **Replay window:** Because inbound dedup is per-account (`LockedConversion` existence) and IBC does
not guarantee exactly-once delivery after TTL, a conversion message could in principle be
reprocessed to re-credit an account (e.g. one that had already `unlock`-ed its prior lock),
drawing repeatedly on the pallet account.
- **Exploitability / realized loss:** Not quantified here — see "Actions taken" for the on-chain
review needed to determine whether any conversion was processed while disabled or replayed on a
live network.
- **`unlock` deliberately unaffected:** the fix intentionally leaves `unlock` callable while
disabled so users can always release already-migrated, legitimately-held funds; `unlock` touches
only the caller's own hold and mints nothing.
### Theoretical worst case:
The naive ceiling was the whole pallet account, but the vesting-slash and slot-freeing mechanics
throttled and taxed the attack heavily. Three nested bounds applied:
**1. Gross ceiling — the till.** The replay re-ran `process_conversion` → `fund`, and `fund` was a
plain `T::Currency::transfer(pallet_account → target, amount, Preservation::Protect)` — it **moved
funds out of the token-conversion pallet account**, it did not mint, and `Protect` kept the account
above its existential deposit. So every replay drew down that one account and, once empty, `fund`
would have failed. The gross ceiling was therefore its drainable balance. The mainnet pallet account
was `PalletId(*b"tcmaipid").into_account_truncating()` = `0x6d6f646c74636d61697069640000…`
(SS58 `5EYCAe5jXDUbcYZWt8V1v6rWXGtjSsJzuHEnpy2kXtx3N8tu`); its `System::Account` at the finalized
head on 2026-07-23 (`0xb97d1fd58bf8faaff26b33bc8ef90eb02cdcb1f0ac5c5edf57a1a559b756e0f7`) held
**free 6,663,034.33 ACU, reserved 0, frozen 0 → gross ≈ 6.66M ACU** (for reference, 3,426
conversions totalling 58.3M ACU had been processed over 2025-12-01 → 2026-05-11; most had since
been withdrawn, leaving 6.66M in the till).
**2. The slot had to be re-opened, and re-opening it was taxed.** A replay to an account was deduped
while that account's `LockedConversion` slot existed, so to replay again the holder first had to
`unlock` to clear it. But `unlock` was gated by `MinLockDuration` (**84 days**) and applied a
vesting slash: it kept `amount_factor = lock_progress / MaxLockDuration` and slashed the rest to the
**Treasury** (`OnSlash = ResolveTo`). At the earliest possible unlock
(`lock_progress = MinLockDuration`), with `MaxLockDuration = 1344 days`:
```
keep factor f = MinLock / MaxLock = 84 / 1344 = 1/16 = 6.25%
```
So to recycle a slot for the next replay the holder **forfeited 15/16 (93.75%) of the amount to the
Treasury and kept 1/16**. Per min-lock cycle: pallet −`amount`, attacker +`amount/16`, Treasury
+`15·amount/16`. Draining the entire till this way would have netted the attacker only
**≈ 6,663,034 / 16 ≈ 416,440 ACU**, with **≈ 6,246,595 ACU slashed back to the Treasury** (i.e. the
protocol would have recovered it — drained from the bridge but not stolen).
**3. It was rate-limited, and the rate cap was strategy-independent.** Net gain per replayed message
was `f · amount` and the wait between replays was `lock_progress ≥ MinLock`, so the net extraction
rate was `f · amount / lock_progress = amount / MaxLockDuration` — **independent of the chosen lock
duration**. Unlocking early (min lock) drained the *gross* till faster but yielded the same *net*
per unit time; holding to full `MaxLock` avoided the slash but took 1344 days. Either way an
attacker would have netted at most **`amount / MaxLockDuration` ≈ 27% of a replayed amount per
year**, and the recipient was fixed to the message's own account (funds could not be redirected), so
this was per self-owned migration, not a free-for-all.
**Realistic pre-containment window.** Replay only became possible ≥ `MinLockDuration` after a
migration (earliest unlock ≈ 2026-02-23, 84 days after the first conversion) and ended at
containment on 2026-07-20 — a window of ≈ 147 days ≈ 1.75 min-lock cycles. In that window an
attacker could have completed at most ~1–2 replay cycles per slot, extracting ≤ `147/1344 ≈ 11%` of
a migrated amount per slot net (the rest slashed to Treasury).
**Bottom line.** Gross bridge-till exposure was ≈ **6.66M ACU**, but net attacker profit was bounded
by the vesting slash to **≤ ~416k ACU** (1/16, only if the whole till were cycled) with the
remainder recovered by the Treasury, and was further throttled to `amount / MaxLock` (~27%/yr) — so
in the realistic ~147-day pre-containment window the extractable net was a low-single-digit-percent
fraction of migrated value. As of `9b84db3d` the inbound `MessageProcessor::process` path was gutted
to a no-op, so the replay path was closed and the live exploitable amount dropped to **0** (it would
return only if the bridge were re-activated). Realized loss (whether any replay actually executed)
is a separate on-chain review, still outstanding — see *Follow-ups*.
### Timeline:
(all times UTC+01:00, from commit history)
| Time | Event |
|------|-------|
| 2026-07-20 | Reported by a user |
| 2026-07-20 10:00 | Miti |
| 2026-07-20 17:14 | Fix: `Self::ensure_enabled()?;` added to `retry_convert`, `retry_convert_for`, `retry_process_conversion`, `retry_process_conversion_for`, and `process_conversion` (`df3f0f07`) |
| 2026-07-20 18:07 | Containment: hyperdrive `send_to_proxy` (outbound) and `MessageProcessor::process` (inbound `ActionExecutor` dispatch) gutted to no-ops — bridge deactivated (`9b84db3d`) |
## Lessons Learned
### What went wrong
- The **weak delivery guarantees of the message protocol were overlooked.** The underlying transport
provides at-least-once (not exactly-once) delivery by design — a message can legitimately be
re-delivered after its TTL — but the pallet's replay protection was not built to withstand that. It
relied on transient, per-account state (`LockedConversion`) that is cleared on `unlock`, instead of
durable per-message (id/nonce) accounting. The protocol behaving as specified is not the fault;
designing dedup as if delivery were exactly-once is.
- The emergency kill switch was intentionally scoped to `convert` only (a deliberate trade-off to let
in-flight conversions complete), which left it unable to halt the replay path. A kill switch is only
as strong as the state-changing paths it actually covers.
### What went well
- A defense-in-depth response was available: closing the specific guard gap (`df3f0f07`) **and**
deactivating the whole bridge (`9b84db3d`) as containment while the incident was assessed.
- The fix correctly preserved user access to funds by keeping `unlock` open when disabled.
### Where we got lucky
- The strongest fund-creating path (`process_conversion`) still had the per-account
`LockedConversion` idempotency check and the `ReceiveFrom` sender check, which limited trivial
duplication while a lock was live and constrained who could originate messages.
### Conclusion
**Actions taken:**
- Enforced `ensure_enabled()` on every state-changing conversion path except `unlock` (`df3f0f07`).
- Deactivated hyperdrive send/receive as containment so no cross-chain action executes while the
incident is contained (`9b84db3d`).
- Shipped as `0.26.3` / `spec_version` 14 (`7cacdc07`).
**Follow-ups:**
- Move inbound replay protection to durable **per-message (id/nonce)** dedup in token-conversion,
rather than relying on `LockedConversion` existence plus the IBC TTL behavior.
- Add tests asserting that, with `Enabled = false`, every path except `unlock` rejects, including the
inbound `process_conversion` message path.
- Define and document the re-activation procedure for hyperdrive (reverting `9b84db3d`) once the
cross-chain stack has been re-reviewed.
---
## Real Decentralized Compute Network
# Real Decentralized Compute Network - Powered by Phones
Serverless compute on a global network of smartphone-based processors. Verifiable execution via Trusted Execution Environments (TEEs). Access with ACU or USDC.
## Get Started - pick your path
🛠️ Developers →
Deploy apps, APIs, or LLMs to the network.
Quickstart · CLI · SDK · Deploy Agent · Examples
📱 Compute Providers →
Run a phone, earn ACU rewards.
Hardware · Install guide · Rewards · Benchmarks
🪙 Token Holders →
Claim, stake, delegate, govern.
Wallets · Vested ACU · Staking · Governance
📊 Discover Acurast →
Tokenomics, traction, audits, roadmap.
Token · Metrics · Whitepapers · Audits
Curious how it works under the hood? **[Read the protocol architecture ↗](/acurast-protocol/architecture/architecture)**
## What is Acurast
Acurast is redefining compute by utilizing billions of smartphones - no data centers required. This verifiable, scalable, and confidential compute network enables developers to run secure applications on decentralized infrastructure at scale, without compromising speed or privacy.
Acurast has already onboarded around 300,000 compute units worldwide, making it the most decentralized verifiable compute network available today. This compute already powers mission-critical workloads with high-security and AI requirements - from REST APIs and webhooks to LLM inference and confidential data processing.
This isn't just another DePIN protocol - it's a fundamental rethink of how the world computes. Instead of relying on data centers or even servers, Acurast harnesses the world's most abundant, powerful, and secure form of compute: the smartphone.
## Why Acurast
As global demand for computing power continues to skyrocket, control has gradually shifted into the hands of a few large, centralized providers. With AI's rapid growth further accelerating the need for massive compute resources, the call for a truly decentralized, global-scale network - by the people, for the people - has never been stronger.
The only way to deliver on this vision is by rethinking decentralized compute from the ground up. Instead of relying on data centers or even servers, Acurast harnesses smartphones: secure by hardware design, abundant in supply, distributed by default.
### Decentralized by design
Tens of thousands of phones serve as compute units across the globe - no centralized data centers required. United by a growing community of Compute Providers active in 175+ countries, this network sets a new standard for true decentralization.
### Scalable by design
By tapping the compute of billions of smartphones worldwide, Acurast is primed to reach unprecedented scale. Backed by academic research benchmarking its performance against leading centralized providers, the protocol is built for the future of decentralized compute. Battle-tested: around a billion transactions processed demonstrate its scalability and resilience.
### Secure by design
Acurast safeguards significant digital assets across leading networks - Bitcoin, Ethereum, Tezos, Polkadot, peaq, and others. The smartphone's best-in-class Trusted Execution Environments (TEEs) confirm that hardware is genuine rather than fake, and maintain confidentiality without requiring trust in the device owner. This emphasis on verifiability and confidentiality delivers the robust security essential for mass adoption of decentralized compute.
## Frequently Asked Questions
What is Acurast?
Acurast is a decentralized cloud compute network powered by smartphones. Developers deploy serverless workloads - REST APIs, webhooks, scheduled jobs, LLM inference, confidential computation - to a global pool of smartphone processors that execute the code inside hardware-backed Trusted Execution Environments (TEEs). Compute Providers earn ACU rewards by running the processor app on their phones.
How is Acurast different from other DePIN networks?
Most DePIN networks coordinate hardware (storage, bandwidth, GPUs in data centers) without verifying what runs on it. Acurast uses smartphone TEEs to **prove** that the deployed code ran unmodified, on genuine hardware, with confidential inputs the device owner cannot inspect. The result is verifiable, confidential compute - not just decentralized compute.
What can I deploy on Acurast?
Anything that runs in a Node.js or Cargo environment: web servers, API integrations, scheduled jobs, Telegram bots, headless browsers (Puppeteer), WebAssembly modules, and LLM inference. See [Example Apps](/developers/examples) for ready-to-clone projects, or jump to the [Quickstart](/developers/getting-started/quickstart).
How do I deploy something quickly?
Three entry paths:
- **[CLI](/developers/tools/cli)** - interactive local workflow: `npx @acurast/cli new my-project` then `acurast deploy`.
- **[SDK](/developers/tools/sdk)** - programmatic deploys from TypeScript/JavaScript for CI and backends.
- **[Deploy Agent](/developers/tools/deploy-agent)** - pay with USDC on Base via x402, no ACU account needed. Best for AI agents.
Pick one in the [Quickstart](/developers/getting-started/quickstart).
How do Compute Providers earn?
Install the Acurast Processor app on a supported smartphone, register it on-chain, and start earning ACU as your phone executes deployments. See [Become a Compute Provider](/processors/become-compute-provider) for the full setup.
What is the ACU token used for?
ACU pays for compute, rewards Compute Providers, secures the network through staking, and grants governance rights. See [Acurast Token](/discover/acurast-token) and [Tokenomics](/discover/tokenomics).
---
## Post-mortem - Acurast Air Drop
**Date:** 2026-02-02
**Authors:** Mike Godenzi
**Status:** Complete
---
## Summary
On the 27th of January 2026, Acurast launched the Air Drop event that allowed users of the Cloud Rebellion to receive ACU tokens on Acurast Mainnet.
The received tokens were supposed to be locked in a vesting schedule for 24 months. Unfortunately some users were able to receive the ACU tokens partially or fully unlocked.
## Impact
Some of the users that claimed their Air Drop received the claimed ACU tokens partially or fully unlocked. Before the system was shut down, around 600'000 ACU tokens were claimed.
## Detection
Users reported being able to transfers the tokens that were just claimed for the Air Drop.
## Root Causes
The Air Drop functionality is implemented in the [pallet-acurast-token-claim](https://github.com/Acurast/acurast-substrate/tree/acurast-v0.23.14/pallets/token-claim) pallet.
The claim functionality would transfer the claimed amount to the provided address and setup a vesting schedule through Substrate's [pallet-vesting](https://docs.rs/pallet-vesting/latest/pallet_vesting/) pallet. This vesting pallet places a lock on the user's account balance for the amount specified, which in this case is the Air Drop claimed amount. Users are then able to call the `vest` extrinsic on `pallet-vesting` to released from the lock the vested token so far.
Users are also able to convert cACU token from the Acurast Canary network to Mainnet ACU tokens via the [pallet-acurast-token-conversion](https://github.com/Acurast/acurast-substrate/tree/acurast-v0.23.14/pallets/token-conversion) pallet. The converted tokens are placed in a `Hold` state on the user's account.
The issue occurred because the lock used by `pallet-vesting` to lock up the claimed token is an overlapping lock that also takes into consideration the balance on `Hold`. This means that if a user used both the cACU to ACU conversion feature and the Air Drop claim, the converted balances would be counted in the locked balance in the vesting pallet, allowing the additional claimed Air Drop amount to be unlocked.
## Trigger
Any user using both the cACU to ACU conversion and the Air Drop claim features would trigger the issue described above.
## Resolution
At first, the Air Drop feature was disabled, then it was re-implemented to not use the [pallet-vesting](https://docs.rs/pallet-vesting/latest/pallet_vesting/) pallet.
Instead now the claimed amount is not right away sent to the user's account, but rather booked internally by the [pallet-acurast-token-claim](https://github.com/Acurast/acurast-substrate/tree/acurast-v0.23.14/pallets/token-claim) pallet and only released to the user's account when the `vest` extrinsic is called.
---
## Action Items
| Action Item | Type | Owner | State |
|-------------|------|-------|-------|
| Disabling of Air Drop feature | mitigate | Andreas Gassmann | DONE |
| Implementation of long term fix | prevent | Simon Wehrli | DONE |
| Acurast Mainnet runtime upgrade to deploy fix | process | Mike Godenzi | DONE |
---
## Timeline
- **2026-01-27 12:30 UTC** - Air Drop functionality released
- **2026-01-27 14:30 UTC** - Air Drop issue surfaced and functionality disabled
- **2026-02-03** - Corrected Air Drop functionality re-released
---
## Lessons Learned
### What went well
Once the issue was surfaced, the Air Drop functionality was quickly disabled.
### What went wrong
The issues took too long to surface.
### Conclusion
It is not trivial to coordinate different subsystems that handle receiving and locking of some tokens on a user's account in a way that do not interfere with each other.
Either the subsystems need to tightly coordinate and carefully lock/unlock the proper amounts, or they should be fully independent and do not use the shared locking system.
Going forward, similar features can be fully tested on the Canary network before enabling them on Mainnet.
---
## Processor Lite and Core
The Acurast processor is the app that is running on smartphones which take part in the Acurast Decentralized Compute Network. Acurast Processors are the infrastructure providers for Acurast's decentralized compute network. Processors provide the compute power of their phone and are used by developers to run their Deployments.
## Acurast Processor Core
_Only available for Android_
Provide compute with a dedicated phone, completely locked down and setup as a compute provider and join Acurast's decentralized compute network. Processor Core transforms your Android device into a dedicated compute node, offering maximum performance and reliability.
**Key Features:**
- **Dedicated Setup**: The device runs only the Acurast Processor app with everything else completely locked down
- **Maximum Performance**: Optimized for continuous operation and higher deployment execution rates
- **Enhanced Security**: Factory-reset device ensures a clean, secure environment
- **24/7 Operation**: Designed to run continuously, maximizing your reward potential
- **Priority Selection**: Developers often prefer Core devices for longer-running, critical deployments
**Requirements:**
- Android 12 or newer
- Device must not be rooted
- Locked bootloader required
- Factory reset necessary during setup
**[Setup Guide ↗](/processors/become-compute-provider#step-4-install-and-setup)**
## Acurast Processor Lite
Provide compute with your everyday phone and join Acurast's decentralized compute network. Processor Lite allows you to contribute compute power during edge-times (like while you sleep and charge) without dedicating an entire device.
**Key Features:**
- **Use Your Everyday Phone**: No need for a dedicated device - runs alongside your regular apps
- **Flexible Operation**: Provide compute during edge-times when your phone is idle (e.g., while charging overnight)
- **No Factory Reset**: Install and start providing compute immediately without wiping your device
- **Cross-Platform**: Available on both Android and iOS
- **Easy Setup**: Simple installation process with minimal configuration required
- **Android Work Profile**: Uses isolated work profile on Android for enhanced security and privacy
**Requirements:**
- **Android**: Android 12 or newer (non-rooted, locked bootloader)
- **iOS**: iPhone 6S or newer (iOS 15+)
**[Google Play ↗](https://play.google.com/store/apps/details?id=com.acurast.attested.executor.sbs.canary)**
**[Apple App Store ↗](https://apps.apple.com/us/app/acurast-processor/id6517361921)**
**[Setup Guide ↗](/processors/become-compute-provider#step-4-install-and-setup)**
## Security
For information about security audits, please visit **[Security Audit ↗](/acurast-protocol/audits)**.
---
## Become an Acurast Compute Provider
Join Acurast's decentralized compute network and start receiving rewards by providing compute power with your smartphone. This guide will walk you through everything you need to get started as a Compute Provider.
## What is a Compute Provider?
Compute Providers are people who run the Acurast Processor App to provide infrastructure for Acurast's decentralized compute network. As a Compute Provider, you provide the compute power of your phone to developers who run their applications (called Deployments) on the Acurast network. In return, you receive rewards in the form of tokens.
## What you need to become a Compute Provider
Getting started as a Compute Provider is simple. Here's what you need:
**One or more Compatible Smartphones**
- **Android**: Android 12 or newer (non-rooted, locked bootloader)
- **iOS**: iPhone 6S or newer (iOS 15+)
- Higher specs (CPU, RAM, Storage) will potentially result in higher rewards
- Check the [Recommended Phones list ↗](https://docs.google.com/spreadsheets/d/1ZvzmMVey4CM2tuif_zJfWiIxH1qkgA-l7BNJMw4vh54/edit#gid=1844886586) for optimal reward potential
**Internet Connection**
- Stable Wi-Fi or mobile data connection
- Reliable connectivity ensures consistent uptime and maximum rewards
**Power Source**
- Keep your device charged or connected to power
- Continuous operation maximizes your rewards
**Acurast Wallet**
- Created automatically when you install the Acurast Processor app
- Or import an existing wallet if you already have one
That's it! No technical expertise required - the Acurast Processor app handles everything else.
## Recommended Phones
Make sure to check out if your phone is on the list of recommended phones. In general, phones with higher specs will receive more rewards.
:::info
**[Recommended Phones ↗](https://docs.google.com/spreadsheets/d/1ZvzmMVey4CM2tuif_zJfWiIxH1qkgA-l7BNJMw4vh54/edit#gid=1844886586)**
:::
## Quick Start Guide
### Step 1: Check Device Compatibility
Before getting started, make sure your device meets the requirements:
**Android Requirements:**
- Android 12 or newer
- Device must not be rooted
- Bootloader must be locked
**iOS Requirements:**
- iPhone 6S or newer
- iOS 15 or newer
### Step 2: Get an Acurast Compatible Wallet
Before setting up your Processor, you'll need a wallet to receive your rewards.
**Recommended Wallets:**
- **[AirGap Wallet ↗](https://airgap.it/)** - Air-gapped self custody wallet
- **[Talisman ↗](https://talisman.xyz/)** - Full-featured Substrate wallet
- **[SubWallet ↗](https://www.subwallet.app/)** - Multi-chain Substrate wallet
For more supported wallets and detailed wallet setup guides, see the [Wallet Documentation](/token-holders/wallets/wallet-overview).
**Quick Setup (Talisman):**
1. Install the [Talisman browser extension ↗](https://talisman.xyz/)
2. Click "New Wallet" and create a strong password
3. Write down your recovery phrase and store it safely (never share this!)
4. Add Acurast network: Click the network dropdown → Search for "Acurast" → Enable it
### Step 3: Choose Your Processor Type
Acurast offers two types of Processors:
**Acurast Processor Core** (Android only)
- Dedicated device setup for maximum performance
- Requires factory reset of your Android phone
- Only the Acurast Processor app will run on this phone (everything else is completely locked down)
- Best for serious compute providers
- [Setup via Acurast Hub ↗](https://hub.acurast.com/)
**Acurast Processor Lite** (Android & iOS)
- Runs on your everyday phone in edge-times (e.g. while you sleep and charge)
- No factory reset required
- Easy to get started
- [Download for Android ↗](https://play.google.com/store/apps/details?id=com.acurast.attested.executor.sbs.canary)
- [Download for iOS ↗](https://apps.apple.com/us/app/acurast-processor/id6517361921)
### Step 4: Install and Setup
#### For Processor Core
1. Factory reset your Android phone
2. Visit [Acurast Hub ↗](https://hub.acurast.com/) and connect with your wallet
3. Follow the detailed setup instructions for dedicated processors
4. Lock down the device as a dedicated compute provider
#### For Processor Lite
1. Download the app from [Google Play ↗](https://play.google.com/store/apps/details?id=com.acurast.attested.executor.sbs.canary) or [Apple App Store ↗](https://apps.apple.com/us/app/acurast-processor/id6517361921)
2. Open the app and give the required permissions
3. Visit [Acurast Hub ↗](https://hub.acurast.com/), connect your wallet and use 'Onboard with hub'. Alternatively tap "Get Started" and a wallet will be created for you on the app.
4. Complete the initial setup wizard
5. Keep your device connected to the internet
### Step 5: Start Receiving Rewards
Once your Processor is set up and running, you'll automatically start receiving rewards:
**Base Benchmark Rewards**
- Receive ACU tokens for providing compute power based on four benchmark metrics of processors
- Receive +10% benchmark metric bonus, when your processors are matched to execute deployments
- Higher-spec devices typically receive more Base Benchmark Rewards
**Staking Rewards**
- Receive ACU tokens for staking your compute
- Receive +10% benchmark metric bonus, when your processors are matched to execute deployments
- Staking more tokens for a longer duration generates more staking rewards
**Cloud Rebellion Points**
- Join the [Cloud Rebellion ↗](https://rebellion.acurast.com/) to receive MIST points
- Complete quests and onboard more Compute Providers
- Invite others to join and receive additional rewards
## Best Practices
To maximize your rewards and ensure smooth operation:
- **Keep your device connected to the internet** - Your Processor needs stable internet to receive and execute deployments
- **Ensure adequate power** - Keep your device charged or connected to power
- **Use recommended devices** - Higher-spec phones from the recommended list will receive more benchmark rewards
- **Run multiple processors** - Scale your compute provision by running [multiple devices](/processors/multiple-processors)
- **Monitor performance** - Check your benchmark scores and uptime regularly
## Next Steps
Now that you're set up as a compute provider, explore these resources:
- Learn about [Processor Lite and Core](/processors/acurast-processors) in detail
- Understand how [Benchmarks](/processors/benchmarks) affect your rewards
- Scale up by running [Multiple Processors](/processors/multiple-processors)
- Join the [Cloud Rebellion ↗](https://rebellion.acurast.com/)
- Follow us on [X ↗](https://x.com/Acurast)
- Join the community on [Discord](https://discord.gg/wqgC6b6aKe) or [Telegram](https://t.me/acurastnetwork)
## Need Help?
If you encounter any issues during setup or have questions:
- Check the [FAQ](/faq) for common questions
- Join the [Discord community ↗](https://discord.gg/wqgC6b6aKe)
- Reach out on [Telegram ↗](https://t.me/acurastnetwork)
- Contact support through the [Acurast Hub ↗](https://hub.acurast.com/)
Welcome to the Acurast network!
---
## Benchmarks
Benchmark tests on Acurast phones are vital to determine the compute power of the participating devices. The compute metrics determined by these benchmark tests are relevant for **matching phones** to the right tasks based on their capabilities, **calculating benchmark rewards** distributed for the provision of phones, and **determining [staked compute rewards](/token-holders/staking/overview)**. Better hardware usually leads to a higher benchmark score.
## Overview
The [Acurast Benchmark](https://github.com/Acurast/acurast-benchmark) suite is designed to evaluate the performance of Processors running in the Acurast network. It provides a comprehensive set of tests to measure computational throughput, memory allocation and access efficiency, and storage read/write performance. The benchmarks include support for multithreaded execution to leverage modern multi-core architectures.
The benchmark suite evaluates three main components: **CPU**, **RAM**, and **Storage**. Each component contributes to the overall performance score of the device.
## CPU Benchmarks
The CPU benchmarks evaluate the computational capabilities of the device through three test categories:
### Test Categories
- **Crypto**: Measures encryption and decryption throughput using [AES-256](https://en.wikipedia.org/wiki/Advanced_Encryption_Standard) and hashing throughput using [SHA-256](https://en.wikipedia.org/wiki/Secure_Hash_Algorithms).
- **Math**: Tests matrix multiplication performance, with support for three algorithms:
- [**Divide and Conquer**](https://en.wikipedia.org/wiki/Matrix_multiplication_algorithm#Divide-and-conquer_algorithm): A recursive algorithm used for single-threaded execution when SIMD is disabled or for multithreaded benchmarks in general.
- [**Iterative**](https://en.wikipedia.org/wiki/Matrix_multiplication_algorithm#Iterative_algorithm): A straightforward implementation used when SIMD optimization is enabled but the device lacks the required hardware capabilities.
- **SIMD-Optimized**: An optimized version of the iterative algorithm leveraging SIMD instructions, used when the device supports the required hardware capabilities.
- **Sort**: Benchmarks sorting algorithms, including single-threaded and multithreaded [merge sort](https://en.wikipedia.org/wiki/Merge_sort).
### Execution Modes
The CPU benchmark suite is available in two variations:
- **Single-Core**: Executes all tests using a single thread to evaluate the performance of a single core.
- **Multi-Core**: Executes all tests using multiple threads to leverage the full computational power of the device.
### Configuration
- **Crypto**: Configure the duration of the test and the size of the data to encrypt and hash.
- **Math**: Specify the matrix size, whether to enable SIMD optimizations, and the duration of the test.
- **Sort**: Define the size of the dataset and the duration of the test.
### Score Calculation
The CPU score is calculated as the average of the throughput ($TPS$) values from the Crypto, Math, and Sort benchmarks. Higher throughput results in a higher score.
$$
score_{CPU} = \frac{TPS_{crypto} + TPS_{math} + TPS_{sort}}{3}
$$
## RAM Benchmarks
The RAM benchmarks assess memory allocation and access patterns through two test categories:
### Test Categories
- **Allocation**: Measures the time taken to allocate and initialize memory.
- **Access**: Evaluates sequential, random, and concurrent memory access patterns.
### Configuration
- **Allocation**: Specify the number of iterations and the size of memory to allocate in each iteration.
- **Access**: Configure the number of iterations and the size of data for sequential, random, and concurrent access patterns.
### Score Calculation
The RAM score is calculated based on the inverse of the average times ($T$) for Allocation and Access benchmarks. Higher efficiency results in a higher score. The total available RAM memory is also recorded.
$$
score_{RAM} = \frac{T_{alloc}^{-1} + T_{access\_seq}^{-1} + T_{access\_rand}^{-1} + T_{access\_concurr}^{-1}}{4}
$$
## Storage Benchmarks
The storage benchmarks focus on file I/O performance through read and write operations.
### Test Categories
- **Access**: Measures sequential and random read/write throughput for files on the storage medium.
### Configuration
- **Access**: Define the number of iterations and the size of data for sequential and random read/write operations.
### Score Calculation
The Storage score is calculated as the average of the inversed average times ($T$) for sequential and random read/write operations. Lower average times result in a higher score. The available storage capacity is also recorded.
$$
score_{storage} = \frac{T_{access\_seq}^{-1} + T_{access\_rand}^{-1}}{2}
$$
## Overall Scoring
The compute overall score is calculated as a weighted average that combines performance metrics from CPU, RAM, and Storage benchmarks to provide a comprehensive evaluation of a device's computational capabilities.
$$
score_{all} = w(score_{CPU\ SC}) + w(score_{CPU\ MC}) + w(mem_{RAM}) + w(score_{RAM}) + w(mem_{storage}) + w(score_{storage})
$$
Where:
$$
w(m) = \frac{m}{N_{m}} \times W_{m}
$$
And:
- $W_{m}$: Weight assigned to each metric pool ($0 \leqslant W_{m} \leqslant 1$ and $\sum_{m} W_{m} = 1$)
- $N_{m}$: Normalization factor representing the sum of all scores submitted to the metric pool. Uses the previous epoch's total if available, otherwise the current epoch's total, or defaults to $m$ if no data exists on the chain.
- $score_{CPU\ SC}$: CPU performance score (Single-Core)
- $score_{CPU\ MC}$: CPU performance score (Multi-Core)
- $mem_{RAM}$: Total available RAM memory
- $score_{RAM}$: RAM performance score
- $mem_{storage}$: Available storage capacity
- $score_{storage}$: Storage performance score
To make the final score human-readable, a scaling factor of $10^9$ is applied.
$$
score_{display} = score_{all} \times 10^9
$$
### Component Weights
The components are weighted as follows:
- CPU Single-Core: $W_{CPU\ SC} = 0.2307$
- CPU Multi-Core: $W_{CPU\ MC} = 0.2307$
- RAM Total: $W_{RAM\ mem} = 0.4615$
- RAM Speed: $W_{RAM\ score} = 0$
- Storage Total: $W_{storage\ mem} = 0.0769$
- Storage Speed: $W_{storage\ score} = 0$
If all weights equal $0$, the components are weighted equally:
$$
W_{m} = \frac{1}{n} \text{ if active, or } W_{m} = 0 \text{ otherwise}
$$
Where:
- $n$: The number of active metric pools
---
## Compute Providers
# Provide Compute, Earn Rewards
Run the Acurast Processor app on a supported smartphone and contribute to a global decentralized compute network. Earn ACU for verified uptime and completed deployments.
## Start here
- **[Become a Compute Provider](/processors/become-compute-provider)** — install the app, register, go live.
- **[Supported Hardware](/processors/acurast-processors)** — recommended phones and minimum specs.
## Operate
- **[Multi-Processor Setup](/processors/multiple-processors)** — run several phones from one account.
- **[Rewards](/processors/rewards)** — how payouts work, what you earn, when.
- **[Benchmarks](/processors/benchmarks)** — performance comparisons across devices.
## Related
- Want to stake on providers instead of running hardware? See **[Stake & Delegate](/token-holders/staking/overview)**.
- Curious how the network matches workloads to your phone? See **[Matcher](/acurast-protocol/architecture/architecture#matcher)**.
## FAQ
### How can you provide compute with your phone on Acurast?
Get started as a [Processor](/processors/acurast-processors) on Acurast and provide compute to receive rewards, either with Lite or Core:
#### **Acurast Processor Lite**
Lite is more flexible, can be installed on your everyday phone and activated to provide compute when it's feasible for you. [How to onboard ↗](https://youtu.be/2Pxw2u0pZ_E?si=jHPCk82QI5I7rOKA)
Install the Acurast Processor Lite application on Android or iOS to get started.
[**Google Play ↗**](https://play.google.com/store/apps/details?id=com.acurast.attested.executor.sbs.canary)
[**Apple App Store ↗**](https://apps.apple.com/kh/app/acurast-processor/id6517361921)
#### **Acurast Processor Core**
Core is meant for dedicated phones with the only purpose of providing compute to Acurast, completely locked down. [How to onboard ↗](https://www.youtube.com/watch?v=uLpBRUnmiPY)
**[Acurast Hub ↗](https://hub.acurast.com/)**
### What is the difference between Acurast Processor Core and Lite?
Acurast Processor Core is meant for dedicated phones, completely locked down, and intended only to provide compute to Acurast.
Whereas Lite is more flexible, can be installed on your everyday phone and activated to provide compute when it's feasible for you.
If you have a phone that you don't need any more for anything else, go with Core.
**[Learn more about Acurast Processors ↗](/processors/acurast-processors)**
### What Android and iOS version are required?
The Android version required is Android 12 or higher, while the iPhone must be a 6s or later.
**[Check the list of recommended phones ↗](https://docs.google.com/spreadsheets/d/1ZvzmMVey4CM2tuif_zJfWiIxH1qkgA-l7BNJMw4vh54/edit?gid=1844886586#gid=1844886586)**
### Is there a difference in rewards between Acurast Processor Core and Lite?
As long as your phone is connected to the internet, and the Processor application app is running, you get a reward. You can increase the rewards by staking your ACU tokens, see [Staked Compute](/token-holders/staking/overview).
Typically, Acurast Core scores 15-20% better in the benchmark tests, which will result in higher Base Benchmark Rewards and more Compute that can be committed in Staking.
Read more about the [Rewards here](/processors/rewards).
### I'm already running other Processors. Can I manage them all with one account?
Yes, most likely, you've connected a wallet to the Acurast Hub; follow these steps:
- Go to the Acurast Hub and select "Add New Device" to create a new QR code
- Install Acurast Processor Lite from Google Play or the Apple App Store on the phone
- Open Lite and select "Connect to Hub"
- Scan the QR code
- Complete the setup process, Lite is now connected to the Hub
### How can I tell how many heartbeats a processor has had?
The device heartbeats every 30 minutes. Scanning the blockchain allows you to look for all heartbeats, but that would take some time. To track the recent performance of your Processors, you can use the [Acurast Monitoring Bot](https://t.me/AcurastBot) on Telegram.
### Do I still get rewards if my Processor is missing heartbeats?
**[Benchmark rewards](/processors/rewards)** are distributed if one of 3 heartbeats of an epoch (roughly 90 minutes) was received on chain. This means your device can miss some heartbeats without missing out on rewards.
### Do I need to factory reset my phone?
You only need to factory reset your phone if you are onboarding a Core device. However, for Processor Lite, you can use your everyday device without factory resetting it.
Learn more about the differences between **[Core and Lite ↗](/processors/acurast-processors)**
For step-by-step instructions on getting started, see the **[Compute Provider onboarding guide ↗](/processors/become-compute-provider)**.
### Why is my balance decreasing sometimes?
Your balance may decrease slightly due to transaction fees when your Processor interacts with the Acurast network. Whenever the Processor reports to the chain (heartbeats, deployment acknowledgements, execution reports), minimal gas fees have to be paid, which can result in a decreasing balance. These fees are typically minimal, usually around 0.01 ACU per transaction.
### How do I install Acurast Processor Lite?
Acurast Lite can be installed within minutes. The Acurast Processor Lite application can run on Android or iOS.
**[Follow the step-by-step installation guide ↗](/processors/become-compute-provider#for-processor-lite)**
### Android: Why do I need to set up an "Android Work" profile?
Android Work profiles are a built-in feature of Android, typically used by organizations to separate work apps and data from personal apps on the same device. This creates a secure, isolated container managed by an enterprise or organization.
The Acurast Processor Lite leverages this proven Android feature to create a secure, isolated environment for providing compute. The Work profile acts as a second profile completely independent from your private profile. By keeping your private profile separate from the one that provides compute, you'll get the best security and have no impact on your privacy.
### Android: Who manages the "Android Work" profile?
You do. You're in complete control. Your device is connected to the decentralized Acurast protocol (Acurast Canary) and controlled by you. In no way do any individuals have access to your phone to install other applications in that work profile or make changes to it.
**Important Note:** During setup, you may see standard Android messages like "Your device isn't private" or "Your IT admin may see your data...". These are default warnings that appear whenever a work profile is created. In the case of Acurast, you remain in complete control - these are simply standard messages Android displays for all work profile setups.
### Android: How do I uninstall Acurast Processor Lite?
Because Lite uses the Android work profile, you'll have to remove it completely to uninstall the app from your device.
If you have set up Lite with "Get Started" without a connection to your Acurast Hub, make sure that you backup your secret before removing the app; otherwise, you will lose access to your cACU.
Uninstalling Processor Lite:
1. Open the "Settings" with the cog on the top right on the Acurast Processor application (make sure you can see the compute status)
2. Click "Uninstall" and "Continue"
3. The Processor app in the work profile has been removed, you can now uninstall the lite app from your private profile and the work profile should be removed too (might be subject to Android version).
### How do I install Acurast Processor Core?
Acurast Processor Core is designed for dedicated Android phones. Follow the detailed installation instructions to get started.
**[Follow the step-by-step installation guide ↗](/processors/become-compute-provider#for-processor-core)**
### How to add Acurast Processor Core with NFC?
A short video by a community member onboard a device with a broken screen and an NFC.
**[NFC Onboarding ↗](https://youtu.be/lbAYV1kmV6o?si=_IbU5RfXYdlp5p-w)**
### The "Add New Device" QR code is not showing?
When this happens, you can do two things:
- Disconnect and connect your wallet to the Hub.
- Use another browser.
---
## Run Multiple Processors (Farms)
### Running multiple Acurast Processors
Running multiple Acurast Processors presents additional challenges. For optimal performance and availability, it is recommended to set up each Processor with Acurast Core on a freshly wiped device. Below are recommendations and best practices for managing multiple Acurast devices.
### Use a separate wallet
Each Acurast Processor is managed by a designated manager address. If you plan to run multiple Acurast Processors, ensure they are all managed by the same account. This manager account will be responsible for overseeing all devices and will collect the associated computation rewards.
Good experiences have been made with the following wallets:
- Metamask
- Talisman
- Solana wallets
- SubWallet
- Other EVM wallets via Walletconnect
Set up an Acurast account, back up the seed phrase according to standard best practices, and connect the account to the Acurast Hub. Then, obtain some initial funds from the faucet - these are required to register your first devices on the Acurast chain.
### Onboarding multiple phones at once
To onboard multiple phones simultaneously, users can opt to receive a QR code designed for bulk onboarding. This QR code can also include Wi-Fi access point information, eliminating the need to manually enter it on each device.
1. Connect your wallet to the [Acurast Hub](https://hub.acurast.com)
2. Click _Add Phone_ and sign from your wallet
3. Above the QR code change to _Multi Use_ and sign again
4. A QR code for multiple onboardings will be displayed
5. Toggle the _Advanced_ functions
6. Set the WiFi SSID to your Access Point, enter the WiFi Password for that AP and set the right WiFi Type, click _Save Changes_
7. Ensure phones are wiped, tap 6x on the first screen after starting the phone and scan the displayed QR code to set up the phones
8. Follow the instructions on screen
### Manage the phones in the Hub
The Acurast Hub is the place to manage your Acurast Processors. Users find it on [hub.acurast.com](https://hub.acurast.com)
#### Phones list page
On the _Phones_ page users see a list of phones they have deployed the Acurast Processor to. This page helps you to manage your devices.
- _Acurast address_: Device address with _identicon_ (click on the identicon to copy the device's address)
- _Last seen_: When the last heartbeat was detected
- _Attested_: If the device got attestation
- _Battery_: Battery health information (needs to be activated, see below)
- _Star rating_: Devices reputation, based on successfully completed jobs (default is 0.5)
- _Processor version_: OS and Processor version
- _Status_: Number of currently running Deployments
- _Settings_ (Cog wheel:) Opens the device settings
- _Bin_: Remove the device from your list and deregister it from Acurast
- _Advanced_: Toggle advanced settings
- _Activate management endpoint_ (needed to activate battery monitoring)
- _Enter_ a custom management endpoint (eg. for a self hosted management backend)
- _Processor Ownership:_ Transfer all Processors to a different manager
- _Update Processors_: Sends a signal to trigger the update of the Processor apps (the update can take a few minutes to be reflected)
### Monitoring Processors
#### Telegram Bot
To track the recent performance of the Processors and additional information, users can use the Acurast Monitoring Bot on Telegram: [@AcurastBot](https://t.me/AcurastBot)
#### Battery monitoring
The Acurast Hub offers some battery health and status indicators for every phone. This feature needs to be activated.
1. Connect your wallet to the [Acurast Hub](https://hub.acurast.com)
2. Go to _Phones_
3. Toggle the _Advanced_ functions
4. Toggle management endpoint by setting it to _active_
5. Wait until the devices show a battery indicator (this can take up to 2 heartbeats and a reload of the page)
#### Advanced battery monitoring with self hosted management backend
This tool is intended for advanced users who can host their own software and need to monitor a large number of phones. It also enables integration with external systems — for example, to control smart plugs or trigger third party systems. An early version of the tool is available here: [Acurast Processor Management Backend](https://github.com/Acurast/acurast-Processor-management-backend).
### Practical issues
#### WiFi recommendations
- Ideally, the WiFi access point for Acurast should be separate and not used for other purposes.
- 5Ghz WiFi is preferable if users want to connect many devices as it offers more non-overlapping channels.
- If you run a massive farm, ensure your devices have the network capacity, even if they all are running deployments
#### Safety Recommendations
Running smartphones 24/7 — especially older or partially damaged devices - can accelerate wear and tear, particularly on the battery. Some users have reported battery swelling, which is a sign of degradation and potential risk. While there have been no known fire incidents among Acurast users, all operators, especially those running multiple devices, are strongly advised to take basic safety precautions:
- Avoid placing phones on flammable surfaces.
- Ensure adequate ventilation around devices to prevent overheating.
- Regularly check batteries for swelling or unusual heat. Remove phones with swelled batteries.
- Use certified chargers and power strips with surge protection.
Your safety is a priority - please run your setup responsibly.
---
## Processor Rewards
As a Compute Provider running Acurast Processors, you receive rewards for contributing your device's compute power to the network. The reward system is designed to incentivize reliable compute provision, high-quality hardware, and active participation in the Acurast ecosystem.
## Reward Types
### Base Benchmark Rewards (Compute Pool)
Providers receive rewards based on the [benchmarks](/processors/benchmarks) of their participating devices. These rewards are paid from Acurast's token inflation. On Canary and Mainnet, a total of 10% of Acurast's inflation are distributed per epoch (900 blocks / roughly every 1.5 hours). This equals to 856.164 tokens per epoch to distributed as Base Benchmark Rewards (Compute Pool) among all Compute Providers, independent of their participation in Staked Compute. These rewards are split up to the different benchmarks pools and each phone competes with the others in the 4 benchmark pools. Higher specs usually lead to higher Base Benchmark Rewards (Compute Pool).
Distribution per Benchmark Metric on Canary Network
### Staking Rewards (Staked Compute Pool)
Compute Providers who participate in [Staked Compute](/token-holders/staking/overview) earn additional rewards by committing their compute power and staking tokens. 70% of Acurast's inflation is distributed as Staking Rewards (Staked Compute Pool) every epoch, which is 5'993.15 tokens. Staking Rewards (Staked Compute Pool) are distributed based on hardware performance, stake size, and commitment duration. Learn more about [how Staked Compute works](/token-holders/staking/overview) and how to maximize your staking rewards.
Acurast Inflation Distribution
### Deployment Execution Bonus (aka Busy Bonus)
When a processor is detected executing a deployment during an epoch, based on the first successful heartbeat of that epoch, it receives a bonus weight of 10% on the Benchmark Metrics. This will increase the scoring - and therefore the rewards - in both the **Staked Compute Pool** and the **Compute Pool (Base Benchmarking Rewards)** for that epoch.
The deployment costs for all deployments are paid by the developers and are burned after successful execution, creating a deflationary effect on the total supply.
### Cloud Rebellion
Join the [Cloud Rebellion](https://rebellion.acurast.com/) and harvest MIST points. Become a Rebel and cloud-harvest MIST (points) by completing quests, onboarding Processors, and inviting others to join the Rebellion.
---
## Claiming Vested ACU
Some entities and individuals have received ACU tokens that are vested for a certain period of time, as outlined in the [token allocations](/discover/tokenomics#token-allocations). Vested tokens originate from early funding rounds, team allocations, and airdrops. As time passes, the owners can claim the released tokens.
To claim the released tokens, follow these steps:
1. Log in to [hub.acurast.com](https://hub.acurast.com) using the account that owns the vested tokens.
2. Go to the **Balances** tab.
3. Next to the vested balance, a claim button should be visible and the amount of claimable tokens will be displayed.
4. Click **"Claim"** and sign the transaction when prompted in your wallet.
---
## How to Get ACU
ACU is available on multiple chains. Where you buy it determines which token instance you receive and whether you need to bridge before using it on Acurast Mainnet to stake, delegate, or create deployments.
## 1. Buy ACU on an exchange
ACU is listed on various centralized and decentralized exchanges. Major CEX listings include:
- [Binance Alpha ↗](https://www.binance.com/en/how-to-buy/acurast)
- [Kraken ↗](https://www.kraken.com/learn/buy-acurast-acu)
- [Gate.io ↗](https://www.gate.com/how-to-buy/acurast-acu)
- [KuCoin ↗](https://www.kucoin.com/how-to-buy/acurast)
- [Bitvavo ↗](https://bitvavo.com/en/acurast)
- [MEXC ↗](https://www.mexc.com/price/ACU)
For the full and current list of markets, see [CoinMarketCap ↗](https://coinmarketcap.com/currencies/acurast/#Markets) or [CoinGecko ↗](https://www.coingecko.com/en/coins/acurast).
Each exchange chooses which token instance it supports (Acurast native, ERC20 on Ethereum, OFT on BSC/Base/Peaq). **Check the network before depositing or withdrawing.** Sending the wrong instance to an incompatible address can result in lost funds.
| Exchange type | What you get | Path to Acurast Mainnet |
| ---------------------------------- | ---------------------- | ------------------------------------------------------ |
| CEX with Acurast native withdrawal | ACU on Acurast Mainnet | Withdraw directly to your Acurast wallet |
| CEX/DEX with Ethereum withdrawal | ERC20 ACU on Ethereum | HyperDrive: Ethereum → Acurast Mainnet |
| CEX/DEX on BSC, Base, or Peaq | OFT ACU on that chain | Stargate → Ethereum, then HyperDrive → Acurast Mainnet |
See [Acurast Token](/discover/acurast-token) for the full list of deployed token contracts.
## 2. Pick a wallet
Before bridging or receiving ACU, set up a wallet for the network you target.
- For **Acurast Mainnet** native ACU: see [Wallet Overview](/token-holders/wallets/wallet-overview).
- For **EVM chains** (Ethereum, BSC, Base, Peaq): use any standard EVM wallet (MetaMask, Rabby, etc.).
## 3. Bridge ACU to Acurast Mainnet
Goal: get ACU onto Acurast Mainnet so you can stake, delegate, or create deployments. Path depends on which chain you start from.
```
ACU on BSC / Base / Peaq → (Stargate) → ACU on Ethereum → (HyperDrive) → Acurast Mainnet
ACU on Ethereum → (HyperDrive) → Acurast Mainnet
```
If you already hold ACU on Ethereum, skip to [Step B: Ethereum → Acurast Mainnet](#step-b-ethereum--acurast-mainnet-hyperdrive).
The reverse direction (Acurast Mainnet → Ethereum → BSC/Base/Peaq) works the same way through the same UIs, but is not covered here.
### Step A: BSC / Base / Peaq → Ethereum (Stargate)
ACU on EVM chains is a LayerZero OFT. Use [stargate.finance](https://stargate.finance/) to move ACU from BSC, Base, or Peaq to Ethereum.
**1. Open Stargate and connect your wallet.** Go to [stargate.finance](https://stargate.finance/) and click **Connect Wallet** in the top-right.
**2. Pick your wallet.** Select your EVM wallet (MetaMask, Rabby, Coinbase Smart Wallet, WalletConnect, etc.) from the EVM section.
**3. Approve the connection in your wallet.** Confirm the connection prompt.
**4. Verify your ACU balances.** Once connected, Stargate shows ACU on each chain it supports (Ethereum, BNB Chain, Base, Peaq).
**5. Select source: ACU on BSC, Base, or Peaq.** Click **From** and pick the ACU instance you hold. Set **To** to **ACU on Ethereum**.
**6. Enter the amount.** Make sure you have enough native gas (BNB, ETH on Base, etc.) on the source chain. Stargate warns `Not enough native for gas` if you don't.
**7. Click Transfer.** When the form is valid, the button activates. Review the route (OFT), total fee, and estimated time.
**8. Approve token spending.** First-time transfers require an ERC20 approval. Confirm the spending cap in your wallet.
**9. Confirm the transfer transaction.** Sign the second transaction (the actual bridge call to LZMultiCall).
ACU arrives on Ethereum in ~1–2 minutes. Continue with Step B.
### Step B: Ethereum → Acurast Mainnet (HyperDrive)
Use the official Acurast HyperDrive bridge to move ACU from Ethereum to Acurast Mainnet.
**1. Open the Hub and connect both wallets.** Go to [hub.acurast.com](https://hub.acurast.com) and connect your EVM wallet and your Acurast (Substrate) wallet. The **Balances** page shows ACU per EVM chain and your Acurast Mainnet balance.
**2. Open the Bridge.** Click **Bridge** in the left sidebar. Set **From: Ethereum**, **To: Acurast**. Enter the amount.
**3. Click BRIDGE and sign the transaction.** Confirm the Hyperdrive call in your EVM wallet.
**4. Track progress in History.** The **History** tab shows the transfer state, first waiting, then pending.
**5. Verify arrival.** Once finalized, ACU appears under **Acurast Balance** on the Balances page. Ready to stake, delegate, or use for deployments.
## 4. Verify the token contract
Only interact with token contracts listed in the [official token reference](/discover/acurast-token#overview-of-deployed-acu-token-contracts-and-instances). Beware of fake tokens with similar names.
:::warning CAUTION
Sending ACU to an address on the wrong network is irreversible. Always confirm the destination chain matches the token instance you are sending.
:::
## Related
- [Acurast Token](/discover/acurast-token) — token contracts and instances.
- [Wallets](/token-holders/wallets/wallet-overview) — supported wallets.
- [Claim Vested ACU](/token-holders/claiming-acu) — for early-round and airdrop recipients.
---
## Token Holders
# Hold, Claim, Stake, Vote
Everything you need as an ACU holder: pick a wallet, claim vested tokens, stake on compute providers, and participate in governance.
## Start here
- **[Choose a Wallet](/token-holders/wallets/wallet-overview)** — supported wallets for ACU.
- **[How to Get ACU](/token-holders/how-to-get-acu)** — buy on an exchange and bridge to Acurast Mainnet.
- **[Claim Vested ACU](/token-holders/claiming-acu)** — release tokens from early rounds, team allocations, or airdrops.
## Stake & delegate
- **[Staking Overview](/token-holders/staking/overview)** — what staked compute is and why it matters.
- **[How to Stake](/token-holders/staking/how-to-stake)** — step-by-step.
- **[Staking Mechanics](/token-holders/staking/staking-mechanics)** — rewards, lockups, fees.
- **[Slashing & Risks](/token-holders/staking/slashing)** — what can go wrong.
- **[Mainnet vs Canary](/token-holders/staking/mainnet-vs-canary)** — which network to use.
## Govern
- **[Governance](/acurast-protocol/governance)** — how proposals and voting work.
## Reference
- **[Staking FAQ](/token-holders/staking/staking-faq)**
- **[Staking Glossary](/token-holders/staking/staking-glossary)**
## Related
- New to ACU? See **[Acurast Token](/discover/acurast-token)** and **[Tokenomics](/discover/tokenomics)**.
- Want to run hardware too? See **[Compute Providers](/processors)**.
## FAQ
### What wallets can I use on Acurast?
**[See the full list of supported wallets ↗](/token-holders/wallets/wallet-overview)**
### How can I fund my wallet?
Acurast tokens ACU, are available on various markets, see directories like [CoinMarketCap ↗](https://coinmarketcap.com/currencies/acurast/#Markets) or [CoinGecko ↗](https://www.coingecko.com/en/coins/acurast) for a list of markets.
Depending on the type of token you get (EVM or Acurast native), you might need to bridge it first, before you can send it to the wallet you use for Acurast Mainnet. See token section: [Acurast Token](/discover/acurast-token).
### What is the difference between MIST, cACU & ACU?
- MIST are points, not tokens, used to incentivize rebels via the Cloud Rebellion.
- cACU is the Acurast Canary (incentivized testnet) token.
- ACU is the native token of the Acurast Mainnet.
---
## How To Stake
## How to Stake
On the Staking page in the Acurast Hub, users can stake and have an overview over their Stakes and Delegations.
To create a stake, Compute Providers (aka Committers) have to:
1. Compute: select the amount of Compute they are comfortable to commit.
2. Cooldown: select the Cooldown period. The cooldown period is measured in blocks. The Staking Frontend also provides an estimate expressed in time (days etc.)
3. Tokens: Select the amount of tokens to stake
4. Compound: Select if Autocompoundung is ON or OFF
To do a delegation, Delgators have to:
1. Committer: Select a Committer
2. Cooldown: Select a Cooldown period
3. Tokens: Select the amount of tokens to delegate
4. Compound: Select if Autocompoundung is ON or OFF
## Staking Lifecycle
**Committer:**
* **Start staking:** Committer chooses the amount of computation to commit, tokens to stake, and cooldown length, then creates a stake.
* **While staking:** Committers can add more tokens to the stake, increase duration and committed compute, and either compound or withdraw rewards. They must maintain their committed compute level to avoid slashing.
* **Exit staking:** Committer triggers cooldown (reducing rewards to 50% but maintaining full slashing risk) and waits until it ends.
* **Finalize (Withdraw/Claim):** After cooldown ends, committer finalizes the stake, withdrawing the unlocked funds and any unclaimed rewards.
**Delegator:**
* **Start Staking:** Delegators choose one or several committers to delegate to, then select the amount of tokens and cooldown period (which cannot exceed their chosen committer's cooldown).
* **While staking:** Delegators can add more tokens, increase duration, compound or withdraw rewards. They can also redelegate their stake to a different committer with equal or greater parameters (committed compute, stake size, and cooldown duration).
* **Exit staking:** Delegator triggers cooldown (reducing rewards to 50%) and waits until it ends.
* **Finalize (Withdraw/Claim):** After cooldown ends, delegator finalizes the stake, withdrawing the unlocked funds and any unclaimed rewards.
## Reward compounding (Autocompound)
When creating a stake, committers and delegators can choose whether their staking rewards should be automatically compounded (automatically added back to their stake) or made available for withdrawal. If they choose not to autocompound, rewards accumulate and can be withdrawn at any time - users are then free to move these rewards or manually stake them again. Autocompounding simply automates this process, maximizing reward growth over time without requiring manual action.
---
## Staking Mainnet vs. Canary
Acurast operates two networks - Mainnet and Canary - each with different staking reward structures and purposes.
## Staking on Acurast Canary Network
Canary serves as Acurast's testing and experimental network, where new features are deployed and tested before moving to Mainnet. Staking on Canary pays 5% of the inflation of 856.16 cACU per epoch.
:::warning Canary Stakes And Conversion to Mainnet
When Mainnet goes live, a 90 days conversion phase starts, allowing users to convert their funds from Canary to Mainnet using the Conversion process. In order to convert staked Canary funds to mainnet, all stakes and delegations on Canary need to be unstaked / undelegated. Users are advised to do that as soon as the conversion phase starts, in order to have enough time to convert. Non converted Canary funds and stakes can not be converted to Mainnet once the Conversion phase has ended.
:::
## Staking on Acurast Mainnet
Mainnet is Acurast's production network with a mature reward structure designed for long-term sustainability. Here, 70% of epoch inflation goes to the Staked Compute Pool (staking rewards), while the remaining inflation is distributed to: 15% Treasury, 10% Benchmark rewards, and 5% Collators (block producers).
## Key Parameters Comparison (Mainnet vs. Canary)
| Parameter | Canary | Mainnet |
|-----------|---------|---------|
| Total Epoch Rewards (100%) | 856.16 cACU | 8,561.64 ACU |
| Staking Rewards per Epoch | 42.81 cACU (5% of 856.16) | 5,993.15 ACU (70% of 8,561.64) |
| Base Benchmark Rewards | 0 cACU (0%) | 856.16 ACU (10%) |
| Collator Rewards | 0 cACU (0%) | 428.08 ACU (5%) |
| Treasury | 813.35 cACU (95%) | 1,284.25 ACU (15%) |
| Min Cooldown Period | 600 blocks (~ 1 hour) | 403,200 blocks (~ 28 days) |
| Max Cooldown Period | 28,800 blocks (~ 48 hours)| 19,353,600 blocks (~ 1344 days / 3.68 years)|
| Max Slashing per Epoch | 0.003424657534% of stake | 0.003424657534% of stake |
---
## Overview(Staking)
## Acurast Staked Compute
Acurast is building a decentralized compute network where anyone can contribute processing power using smartphones. To ensure this network stays reliable and doesn't degrade over time, Acurast uses a staking mechanism that creates economic incentives for consistent hardware availability.
### How It Works
**Compute Providers (Committers)** run the Acurast processor app on actual devices and commit to providing specific compute capacity for a period of time. They stake Acurast tokens as collateral - essentially making a promise backed by Acurast tokens. If they keep their hardware online 24/7 and fulfill their commitment, they earn staking rewards based on their compute amount, stake size, and chosen cooldown duration. If they fail, they lose a portion of their stake through slashing.
**Delegators** are token holders who don't run hardware but want to support the network. They delegate their tokens to committers they trust, earning a share of the rewards (minus the committer's delegation fee). This helps strengthen providers' commitments while letting more people participate. However, delegators share the risk-if their chosen committer fails, delegators also get slashed proportionally. They can redelegate to different committers anytime.
### Why This Matters
This creates "skin in the game" for everyone. Providers are economically motivated to maintain their devices and keep capacity online because failure means losing staked capital. Delegators carefully choose reliable providers because they share the consequences.
For developers, this means predictable, reliable compute capacity they can build on. Long-term commitments backed by financial collateral ensure the infrastructure will be there when needed.
The result is a win-win ecosystem: providers earn rewards for reliability, delegators earn passive income, and the network becomes more valuable as participation grows. Governance voting rights for staked tokens are enabled through the Acurast DAO.
---
## Slashing
## How Slashing Works
Slashing is a penalty mechanism designed to ensure committers keep their promise of providing consistent compute power over time. If a committer fails to stay online or their measured compute in any of the four benchmark metrics falls below what they committed - for example, because phones went offline and were not replaced - their stake gets slashed in proportion to the shortfall. For instance, if a committer promised 100 GB of RAM but only delivers 80 GB, they will be slashed proportionally for that 20% shortfall in the RAM metric.
This mechanism incentivizes providers to actively maintain their deployed Acurast phones and farms, ensuring they're always running and replacing capacity with equally powerful devices when needed. Both the committer and their delegators share this risk, as slashing affects all staked tokens proportionally.
### When Slashing Occurs
When a committer's Current Compute falls below their Committed Compute in any of the four benchmark metrics during an epoch, they become slashable for that epoch. Slashing must be triggered on-chain by a **Slasher**- anyone with an Acurast account who detects the violation. Slashing can be triggered earliest in the epoch after the epoch where the commitment was violated.
### How Slashing Penalties Are Calculated
Once slashing is triggered, penalties are calculated independently for each Benchmark Metric where there's a shortfall. The slashing amount for each metric is determined by multiplying the stake amount by the maximum slash rate (0.003424657534% per epoch), the metric's weight, and the percentage shortfall.
For example, if a committer with a 1,000-token stake falls 50% short on RAM (which has a weight of 0.4615), they would be slashed 0.0079 tokens for that metric alone. If they have shortfalls across multiple metrics, each penalty is calculated separately and then summed together - though the total can never exceed 0.003424657534% of the stake per epoch.
### Slashing Examples
_Example 1: Complete Loss of Compute_
A committer has a 1,000-token stake. In an observed epoch, their Current Compute falls 100% short in all four metrics (CPU Single, CPU Multi, RAM, and Storage). Since they failed to provide any of their committed compute, they face the maximum penalty of 0.003424657534% of their stake per epoch, resulting in a total slash of 0.0342 tokens for this epoch. This breaks down as: 0.0079 tokens for CPU Single Core, 0.0079 tokens for CPU Multi Core, 0.0158 tokens for RAM, and 0.0026 tokens for Storage.
_Example 2 (fictional): Partial Loss of exactly 50% Across All Metrics_
A committer has a 1,000-token stake. In an observed epoch, their compute falls 50% short across all four metrics. They are slashed proportionally for this epoch: 0.0040 tokens for CPU Single Core, 0.0040 tokens for CPU Multi Core, 0.0079 tokens for RAM, and 0.0013 tokens for Storage - totaling 0.0171 tokens (0.00171% of their stake per epoch).
_Example 3 (realistic): Variable Shortfalls_
A committer has a 1,000-token stake. In an observed epoch, their compute falls short by different amounts: 12% in CPU Single Core, 15% in CPU Multi Core, 10% in RAM, and 12% in Storage. The resulting penalties for this epoch are: 0.000948 tokens for CPU Single Core, 0.001185 tokens for CPU Multi Core, 0.001580 tokens for RAM, and 0.000316 tokens for Storage - totaling approximately 0.0040 tokens slashed for this epoch.
### What Happens to Slashed Tokens
Of the slashed amount, 10% goes to the Slasher as a reward for detecting the violation, while the remaining 90% is immediately burned, reducing total supply and inflation.
## Slashing Impact
Importantly, committers still earn rewards for any metrics where they met their commitment; only metrics with shortfalls result in slashing. This means partial performance is still rewarded, while failures are penalized proportionally.
Just as rewards flow from committers to their delegators, slashing penalties also affect delegators proportionally based on their stake. When a committer is slashed, each delegator loses the same percentage of their delegated stake as the committer loses from their own stake. However, delegators can redelegate to another committer at any time if they're concerned about their chosen committer's performance.
---
## Staking FAQ
## Understanding the Basics
### What is the Cooldown period?
When a stake is created, the Committer or Delegator chooses a cooldown length. The allowed range differs between networks: on Mainnet, cooldown periods range from 28 days to roughly 3.68 years, while on Canary, they range from 1 hour to 48 hours. When the user decides to unstake, they trigger the cooldown countdown. Only when the cooldown has ended can they withdraw their staked tokens. During cooldown, the reward weight and vote weight are reduced to 50% of the previous value. See the [Mainnet vs. Canary comparison](/token-holders/staking/mainnet-vs-canary) for exact block values.
### What is the reason for the Cooldown Period to exist?
The cooldown period protects the network from sudden losses of compute capacity. Without it, committers could instantly withdraw their stakes and shut down their hardware, leaving the network vulnerable to instability. The cooldown gives the network time to adjust and allows other providers to fill the gap. It also ensures that committers remain committed to their promise: during cooldown, they must continue maintaining their full committed compute (with full slashing risk) even though their rewards are reduced to 50%. This design discourages impulsive exits and rewards long-term commitment, which is essential for building a reliable, predictable compute network that developers and users can depend on.
### What are Risk/Reward Tradeoffs of Staking with Acurast?
Staking with Acurast follows a clear principle: higher commitment equals higher rewards, but also higher risk. Committers who commit more compute, stake larger amounts of tokens, and choose longer cooldown periods earn proportionally greater rewards - but they also face greater slashing penalties if they fail to maintain their committed compute levels. For example, committing 80% of your measured compute yields more rewards than committing 50%, but falling short on that 80% commitment results in larger slashing penalties. Delegators face a similar tradeoff: they can earn staking rewards without running hardware, but they share in their chosen committer's slashing risk. The key is finding the right balance - commit what you can reliably maintain over the long term. Conservative commitments (lower compute percentage, shorter cooldown) offer lower rewards but also lower risk, while aggressive commitments maximize rewards but require consistent, reliable operation of your hardware.
## Getting Started with Staking
### Why do I only see one slider to Commit Compute, when the four metrics are treated separately anyway?
Even though any stake is basically a separate committement for each of the four of the benchmark pools (CPUs, CPUm, RAM, Storage), the staking frontend only shows one slider for simplicity reasons. For example, if the Committer selects to stake 50% of the measured compute, his stake is a committment to keep up 50% of the compute per Benchmark Metric Pool. Means to upkeep 50% of the CPU Single Core Benchmark Metric, 50% of the CPU Multi Core Benchmark Metric, 50% of the RAM Benchmark Metric and 50% of the Storage Benchmark Metric over the lifetime of the stake.
### Why can I only commit 80% of my measured Compute?
The maximum a user can commit is capped at 80% of the currently total measured compute, in order to prevent early Slashings if the Committer's Compute is fluctuating due to processors going offline.
### Why can I only stake 999 tokens if I have 1000?
When a user stakes tokens, all of these tokens will be locked and cannot be moved. This means that the users account won't be able to pay for gas fees caused by processors to report heartbeats and deployment execution reports. This would result in a situation where this account won't be able to receive rewards from benchmarks or computation without getting additional tokens. Therefore a safeguard deposit of 1 cACU / ACU was introduced.
### What is the maximum of tokens a user can stake?
A committer can stake as much as they want up to the limit defined by their compute metric × the allowed ratio. The higher their benchmark (more or stronger devices), the more stake they can back it with. The committer's own stake cannot exceed some multiple of their compute metric capacity (benchmark score). This prevents someone from locking an enormous amount of ACU behind a tiny phone and unfairly capturing rewards.
## Managing Your Stake
### Can a stake be reduced while it exists?
No. Once a stake is set up, it can only be extended - by a longer cooldown period, committing more computation, or adding more tokens. None of these parameters can be reduced in an active stake.
### How many stakes can a Committer have?
A committer can only have one own stake, but they can delegate one additional stake to themselves and they can delegate unlimited stakes to other committers (one stake per committer), allowing them to create stakes with different cooldown periods and size.
### What happens to my Stake when I migrate or when I transfer processors to a different account?
You must unstake and wait for the cooldown to end, before you can migrate to mainnet or transfer processors to a different account. As the cooldown can take up to 48 hours, you should absolutely trigger unstake before the migration window of 90 days ends.
If you move your processors to a different account, the account with the stake will suddenly lose all of its compute and you will be exposed to staking penalties. It's the same as if you would suddenly cut your internet connection.
## Delegation
### How many delegations can a Committer accept?
While the number of delegations is not limited, a committer's delegated stake is limited dynamically by several parameters, in dependence of their current delegations, the state of these delegations and their own staked compute.
### What happens to the Delegator's stake when the Committer starts cooldown?
Delegators can only choose a cooldown period that is shorter (never longer) than the selected Committer's cooldown. For example, if the selected Committer choses a cooldown of 6 months and the Delegator chose 2 months, once the Committer starts cooldown, it will take 4 months until the Delegator's cooldown automatically starts as well.
Delegators can see when their Committers start cooldown and can redelegate their stake to a different Committer, regardless of whether their own cooldown period has already started.
### What happens to the Delegator's stake when their Committer is slashed?
Delegators are affected by slashing and will lose a pro rata share of their stake if their Committer is slashed. Delegators can choose to redelegate their stake to a different Committer.
## Rewards
### How much in rewards can a Committer or Delegator expect?
Staking rewards in the Acurast network are dynamic and cannot be precisely predicted in advance, as they depend on the collective behavior of all participants. Your individual rewards are determined by your share of the total network commitmen - calculated from your benchmark scores, stake size, and cooldown duration relative to all other stakers. As more participants join with varying parameters, or as existing participants adjust their commitments, the reward distribution shifts accordingly. Additionally, rewards are split across four separate benchmark metric pools, meaning your performance in each metric directly impacts your share of that pool's rewards.
Once the system goes live and staking activity stabilizes, estimated Annual Percentage Rates (APR) will become available to help participants gauge potential returns. However, the fundamental principle remains: stronger hardware, larger stakes, and longer commitment periods will always yield proportionally higher rewards compared to participants with lower commitment levels.
---
## Staking Glossary
## Core Concepts
### Compute Provider
(aka Manager): A user that runs the Acurast processor app on one or more phones.
### Committer
(aka Staker, Manager, Compute Provider): A user that is a Compute Provider who commits compute by Staking.
### Delegator
Users that are Staking Acurast tokens with Committer by attaching their stake to a Committer's stake.
### Stake
A Committers commitment, backed by an amount of Committed Compute, an amount of Acurast tokens and a Cooldown period of a chosen length.
## Compute Metrics
### Benchmark Metrics
Four standardized tests that measure a device's computational capacity: CPU Single Core (0.2307 weight), CPU Multi Core (0.2307 weight), RAM Size (0.4615 weight), and Storage Size (0.0769 weight). These metrics are measured during every device heartbeat and reported on-chain to determine Current Compute and calculate rewards and slashing.
### Current Compute
The amount of compute that was measured across all processors in the last epoch for a specific Compute Provider. During that epoch, all devices ideally had written three heartbeats including three benchmark results. In order to determine the Current Compute for an epoch, the first recorded heartbeat of each device is being used.
### Committed Compute
The amount of compute a Compute Provider commits to providing during the lifetime of a Stake, including its Cooldown period.
### Stake Health
The health state of a Stake, regarding the Current Compute in relation to the Committed Compute.
## Staking Mechanics
### Cooldown
A countdown triggered when a user chooses to exit their stake. During the cooldown period, reward weights are reduced to 50% while slashing risk remains at 100%. When the cooldown period ends, the stake can be finalized and tokens become unlocked and transferable again. The cooldown is measured in blocks and ranges differ by network: Mainnet (28 days to ~3.68 years) and Canary (1 hour to 48 hours). See [Mainnet vs. Canary](/token-holders/staking/mainnet-vs-canary) for details.
### Unstake
The process of signaling the intention to end a Stake. Unstaking triggers the Cooldown.
### Finalize
The process of withdrawing a stake after the cooldown period has ended, returning the unlocked tokens and any unclaimed rewards to the staker or delegator. Also called "claiming a finalized stake."
### Recommit
Stakes in cooldown cannot be recommitted. After the cooldown ended, they can be finalized and withdrawn.
## Rewards & Penalties
### Total Staking Rewards
The rewards paid to all Stakers by the inflation of the Acurast Blockchain per epoch.
### Autocompounding
Adding accrued rewards to the existing Stake instead of claiming them.
### Claiming rewards
Requesting the accrued rewards to be sent to the Staker or Delegator.
### Slashing
A penalty that can be forced upon Committers, if they do not succeed in keeping up the amount of Compute they committed to (the Committed Compute).
### Slasher
A user that triggers Slashing for a Stake that did not provide enough Compute to match its Committed Compute.
### Slasher's Reward
A percentage of the slashed amount, that is given to the Slasher as a reward for detecting a violated compute commitment.
## Delegation
### Redelegate
Detaching a running Delegation from one Committer and attaching it to a new Committer. Can only be done if the new Committer shares the exact same or higher parameters (Committed Compute, Staked Tokens, Cooldown duration) than the previous Committer and is not in Cooldown.
Redelegation can only be done if the current delegation is older than 7 days or if the stake of the chosen Committer is in cooldown.
### Delegation Fee
A fee Committers can set once upon creating a Stake and will receive from the rewards of the Delegations attached to their stake. The Delegation fee cannot be increased during the lifetime of a Stake, but it can be decreased.
The delegation fee is expressed as a percentage and is deducted from the delegator's earned rewards before distribution. This fee compensates committers for maintaining reliable hardware and high compute capacity that benefits all delegators staking with them.
**Example:** If a committer sets a 10% delegation fee and their delegators collectively earn 100 tokens in rewards during an epoch, the committer receives 10 tokens as their delegation fee from all delegators' accrued tokens, and the delegators receive the remaining 90 tokens.
### Delegation Capacity
The amount of delegations a Committer can accept.
### Stale Delegation
A Delegation is stale if the Committer it was delegated to did trigger a cooldown which did end. A stale delegation will not earn any rewards and should be finalized.
### Committer in Cooldown
A delegation shows "Committer in cooldown" to indicate that the Committer this delegation was delegated to, started the cooldown. All following rewards will be halved, while slashing penalty will stay the same. Delegators can choose to trigger their own cooldown or to delegate to a different committer.
## Time & Measurement
### Epoch
One epoch equals 900 blocks (approximately 1.5 hours).
### Heartbeat
A sign of life that all Acurast Processors emit in the form of a transaction that is sent to the Acurast blockchain. The heartbeat information also contains the results of a new benchmark test. Usually heartbeat is emitted and recorded once per cycle, which is three times per epoch. If a processor is offline, it will not emit heartbeats.
---
## Staking Mechanics
## What is Compute?
Compute is the processing power and core value the Acurast Network offers to developers. Compute is physically embodied in the smartphones which run the Acurast processor app all around the globe. The main components of these phones, which are important for Acurast are their processors (CPU), the memory (RAM) and the Storage.
### What is Current Compute?
The measured compute of a Compute Provider is displayed as **Current Compute** on the Staking frontend. It represents the current total compute power measured across all of a provider's devices. Compute is broken down into four benchmark metrics: CPU Single Core, CPU Multi Core, RAM, and Storage.
### How Current Compute is measured
Current Compute is measured through four benchmark tests (CPU Single Core, CPU Multi Core, RAM and Storage) conducted on every device running the Acurast processor. These tests run automatically during each device heartbeat (every 30 minutes) and the results are reported on-chain. The first valid benchmark report per epoch (900 blocks / roughly 90 minutes) will set a processor's Current Compute per benchmark metric and it's deployment activity state for that epoch. Read more about the technical aspects of the benchmark tests [here](/processors/benchmarks).
The results of the latest benchmark tests, show on the display of the processor app, if you tap the "Compute Score" card. Each Benchmark Metric card displays the exact results from the last benchmark test. The Relative Compute Score on top represents how this device scores against the global score of all devices in the Acurast network.
Screenshot of the processor app
On the Staking UI, the sum of all benchmark test scores is displayed - summed up per benchmark metric. Again, the Total Current Compute displayed on top, is just a relatve amount. The absolute sum per benchmark metric across all devices of this committer is shown below.
Screenshot of the staking UI
All scoring activities, which lead to rewards and potentially slashings, are done using the four absolute benchmark metrics which were reported in an epoch - not on the singular number that is displayd as "Relative Compute Score" on the processor or as "Total Current Compute" on the Staking UI.
### Benchmark Metric Weights define Benchmark Metric Pools
Each of the four benchmark metrics has a weight assigned to reflect its importance to the Acurast network's computational requirements and resource priorities. These weights ensure that providers are incentivized to maintain balanced, high-quality compute resources, with RAM being the most heavily weighted metric due to its critical role in application performance:
| Benchmark Metric | Weight |
|-----------------|--------|
| CPU Single Core | 0.2307 |
| CPU Multi Core | 0.2307 |
| RAM | 0.4615 |
| Storage | 0.0769 |
These Benchmark Metric Weights define how staking rewards are split between the different Benchmark Metrics pools. For each 1 ACU that is emitted as staking reward, 0.2307 are assigned to the CPU Single Core Pool, 0.2307 are assigned to the CPU Multi Core Pool, 0.4615 is assigned to the RAM pool and 0.0769 is assigned to the Storage pool.
The Staking frontend is showing the four metrics, as measured in the last epoch for each Committer.
## Rewards
Staking rewards are generated from Acurast's token inflation and distributed every epoch to committers and delegators who support the network. The amount each participant earns depends on three key factors: the strength of their committed compute (measured by benchmark scores), the size of their staked tokens, and the length of their cooldown period. In short: stronger hardware, bigger stake, longer commitment = bigger rewards.
## Reward Flow
Staking Rewards trickle down through several pools and are split up:
**Inflation → Staked Compute Pool**
Each epoch, a share of the inflation (70% on Mainnet and 5% on Canary) goes to the Staked Compute Pool. These reward tokens are created by the protocol and minted directly into the Staked Compute Pool.
**Staked Compute Pool → 4 Benchmark metric pools**
From the Staked Compute Pool, the reward tokens are further split between four Benchmark Metric Pools according to the Benchmark Metric Weights that are composeed from the committer's and his delegators' chosen parameters, mainly
- the staked token amount (shared for all metric pools commited into)
- the chosen cooldown periods for these stakes (shared for all metric pools commited into)
- the committed compute (for the metric pool of consideration)
:::info
**Limited stake per commitment (incl. delegations)**
Every committer will attract delegations, depending on his set fee. The total value (own stake + delegations' stake) we call the _commitment stake_. The desired total _commitment stake_ needs careful planning from the committer since it has a limit. Depending on the committer's own stake he can attract more or less delegated stake. The limit consists of two parts, whatever hits first rules:
- the committer needs to provide at least one part out of 10 from the total commitment stake (incl the delegators stakes). This corresponds a ratio of 1:9, own vs delegators stake.
- An additional limit ensures that the stake backing a single farm stays within a reasonable range. This limit prevents someone from locking an enormous amount of ACU behind a small device and unfairly capturing rewards. The limit is derived from a global $\text{target\_weight\_per\_compute}^{(p)}$, that is in turn dependent from the total supply and total onboarded compute power, measured in regular benchmarked:
$
T^{(p)} := \frac{0.8 \times \text{total\ supply}}{\text{total\ benchmarked\ metric}^{(p)}}
$
that expresses the ideal staking rate given the current compute offered over our system.
:::
**Committer's Share → Self vs. Delegators**
A committer's reward is split relative to the token amounts and chosen cooldown periods, between their own weight
$
\text{committer\_weight}_c := \text{stake}_c \times \frac{\text{cooldown}_c}{\text{MAX\_COOLDOWN}}
$
and the total weight of their delegators
$
\text{delegators\_weight}_d := \sum_d{\left[ \text{stake}_d \times \frac{\text{cooldown}_d}{\text{MAX\_COOLDOWN}} \right]}
$
**Benchmark Metric Pools → Committers**
The share of rewards each committer gets depends on a score calculated from following factors, which can be chosen or influenced by the committer when creating the stake.
The reward split is calculated for each Benchmark Metric Pool $p$ separately. The relative score of a participant $c$ compared to other participants is calculated as
$
\text{committer\_score}_c^{(p)} = \min\left[\text{committer\_weight}_c + \text{delegators\_weight}_d, \; T^{(p)} \times \text{metric}_c^{(p)}\right]
$
In each epoch, the staking reward is split into rewards for each metric pool, $\text{REWARD}^{(p)}$, according to the weights described in [Benchmark Metric Weights](#benchmark-metric-weights).
For one specific pool $p$ a committer $cs reward (to be shared with his delegators) is
$
\text{reward}_c^{(p)} = \frac{\text{committer\_score}_c^{(p)}}{\sum_c{\text{committer\_score}_c^{(p)}}}
$
In short: stronger hardware, bigger stake, longer commitment = bigger rewards.
**Delegators Split**
The remaining delegator rewards are split between all delegators of the same committer, according to each delegator's weight. Also the delegation fee is split off to the Committer. The following factors influence delegation rewards for a delegator $d$ towards a committer $c$:
$
\text{delegator\ reward}_d = \text{delegator\ weight}_d - \text{delegation\ fee}_c - \text{potential\ slashings}_c
$
### Where the rewards come from
Acurast inflates its token supply every year, by 5% (currently, can be changed by governance). On Acurast Mainnet, that inflation is split between the following pools:
- 70% → Staked Compute Pool (The rewards for Staking)
- 15% → Treasury
- 10% → Base Benchmark Rewards (independent from Staking)
- 5% → Acurast blockchain block producers (Collators aka Validators)
### When are rewards distributed
Rewards are distributed to every committer and their delegators once every epoch (one epoch is 900 blocks) with the first reported heartbeat of the committer.
## Rewards to be Expected
Staking rewards in the Acurast network are dynamic and cannot be precisely predicted in advance, as they depend on the collective behavior of all participants. Individual rewards are determined by the participant's share of the total network commitment - calculated from benchmark scores, stake size, and cooldown duration relative to all other stakers. As more participants join with varying parameters, or as existing participants adjust their commitments, the reward distribution shifts accordingly. Additionally, rewards are split across four separate benchmark metric pools, meaning performance in each metric directly impacts the share of that pool's rewards.
Once the system goes live and staking activity stabilizes, estimated Annual Percentage Rates (APR) will become available to help participants gauge potential returns. However, the fundamental principle remains: stronger hardware, larger stakes, and longer commitment periods will always yield proportionally higher rewards compared to participants with lower commitment levels.
## Impact of Cooldown on Rewards and Slashings
### Reduced Rewards During Cooldown
When a committer triggers the cooldown period to exit their stake, both their reward weight and their delegators' reward weights are immediately reduced to 50% of the previous value. This means that throughout the entire cooldown period, the committer and all their delegators will earn only half the staking rewards they earned before cooldown was initiated. This reduction reflects the reduced commitment to the network, as the participant has signaled their intention to withdraw.
### Slashing Remains at Full Rate
Importantly, while rewards are halved during cooldown, slashing penalties are not reduced. Committers must continue to maintain their full committed compute throughout the cooldown period, and any shortfalls will result in standard slashing penalties calculated at 100% of the normal rate. This ensures that committers cannot reduce their hardware commitment once they've initiated cooldown - they must maintain their promised compute capacity until the cooldown period ends and they finalize their stake.
---
## Wallets
A non exhaustive list of wallets supporting Acurast:
- [AirGap](https://airgap.it/)*, see a comprehensive guide here: **[Use AirGap with Acurast ↗](/token-holders/wallets/wallets-airgap)**
- [Talisman](https://talisman.xyz/)*, see a comprehensive guide here: **[Use Talisman with Acurast ↗](/token-holders/wallets/wallets-talisman)**
- [SubWallet](https://www.subwallet.app/)*, see a comprehensive guide here: **[Use SubWallet with Acurast ↗](/token-holders/wallets/wallets-subwallet)**
- [Nova Wallet](https://novawallet.io/)
- [Phantom](https://phantom.com/)
- [WalletConnect compatible wallets](https://reown.com/)
- [Metamask](https://www.metamask.io)
- [Coinbase Wallet](https://www.coinbase.com/wallet) — requires "Base mode" to be disabled, see guide: **[Base Wallet ↗](/token-holders/wallets/wallets-base)**
\* Recommended wallets
## Wallet compatibility
Acurast accounts can be created and managed across several wallets, but moving an account from one wallet to another isn't always straightforward. Different wallets use different cryptographic methods to derive accounts from a secret (mnemonic), which means importing the same seed phrase into a different wallet will often produce a *different* Acurast account rather than restoring the original one.
The matrix below shows which wallet-to-wallet transitions will successfully reproduce the same Acurast account. A ✓ means the account can be imported from one wallet into another and result in the same address; a ✗ means the resulting account will differ. Some combinations involving AirGap work only when a specific derivation path is set — see the footnotes for details.
| **From ↓ / To →** | Acurast Lite | SubWallet | Talisman | MetaMask | AirGap |
|---|---|---|---|---|---|
| **Acurast Lite** | — | ✗ | ✗ | ✗ | ✗ |
| **SubWallet** | ✗ | — | ✓ | ✗ | ✓¹ |
| **Talisman** | ✗ | ✓ | — | ✗ | ✓² |
| **MetaMask** | ✗ | ✗ | ✗ | — | ✗ |
| **AirGap** | ✗ | ✗ | ✓³ | ✗ | — |
¹ SubWallet → AirGap: Set derivation path in AirGap to `/m`
² Talisman → AirGap: Set derivation path in AirGap to `/m`
³ AirGap → Talisman: Set derivation path in Talisman to `//44//434//0/0/0`
---
## AirGap Wallet
# Secure offline setup of AirGap for Acurast
AirGap is a self-custody solution, developed by Papers AG, with a two-device approach:
- **Offline signer** holding the private keys
- **Online wallet** to execute transactions
Signing payloads are transported via QR codes.
## 1. Make the offline phone ready and install AirGap Vault
:::info
You can also download AirGap Vault from the Google Play or Apple App store and take the phone offline afterwards.
For the best possible security, follow the steps below.
:::
1. Get a new phone that will only be used for offline signing and never has a connection to the internet.
- An Android phone is recommended.
- Also get a fresh USB stick which you can connect to the phone.
2. You don't need to log in to a Google Account, but you need to set a **PIN code, pattern, fingerprint or face-id** for your device.
3. Start the phone and update it to the latest OS.
4. Take the phone offline, remove all previous Wi-Fi connections (so it can't connect accidentally), and set it to **Airplane Mode**.
5. Download the latest [AirGap Vault APK from GitHub](https://github.com/airgap-it/airgap-vault/releases) and move it to the USB stick.
6. Plug it in and install the APK on your phone, give the necessary permissions.
## 2. Install AirGap Wallet on the online phone
7. On the online phone, install AirGap Wallet from the [Google Play Store](https://play.google.com/store/apps/details?id=it.airgap.wallet) or [Apple App Store](https://apps.apple.com/app/airgap-wallet/id1420996542).
## 3. Generate a new secret
8. Open the AirGap Vault app, skip through the initial messages and accept the disclaimer.
9. Select the **offline configuration** and skip to the screen where you can generate a new secret.
10. Select **Generate** and give permissions for camera and microphone.
11. Go through the entropy generation process (touch, gyro, camera, mic).
12. Write down the secret recovery seed phrase and store it according to best practices.
- ❌ Do not store the seed phrase on an online device.
- ❌ Do not store it in an online password manager.
13. Verify the written-down recovery seed phrase.
14. Set an **encryption password** and ensure you always remember it.
- ✅ You may use a password manager here (but never store the seed in it).
## 4. Generate an Acurast account and sync with AirGap Wallet
15. Add an Acurast account and confirm with your encryption password and the device PIN. A new Acurast address will be generated.
16. Click on the new Acurast account, then click on the **AirGap Wallet** button → a QR code will be displayed.
17. Open AirGap Wallet and scan the QR code of the offline device.
- The account's **public key** will be imported into your AirGap Wallet.
## 5. Verify the recovery
18. In AirGap Vault, go back to the main screen.
19. Click on the card of the newly generated secret.
20. Tap on the 3-dot menu → **Secret management** → scroll down and select **Delete** → Confirm Secret Removal.
- The secret is now wiped from the offline phone.
21. Go back to the main screen and select **Import**.
22. Import the secret recovery phrase from your written notes.
23. Set the same encryption password as before.
24. Once done, generate an Acurast account.
25. Compare the recovered account to the account synced in step 17.
- ✅ If they match → recovery successful.
- ❌ If not → delete and start again from step 10.
## Important to know
1. The **encryption password** set in step 14 will determine the derived account.
- If you forget it, recovery is impossible even with the seed phrase.
2. Follow best practices when storing the seed phrase:
- Example: metal plate in safe, paper note in safe, ensure you can identify the right seed.
3. Do not store the encryption password in the same place as the recovery seed.
4. More documentation: [AirGap Support](https://support.airgap.it/)
5. **NEVER** connect your offline phone to the internet.
- If you must, first completely remove the secret from the phone.
6. If you need to copy the account address, copy it from the online phone with AirGap Wallet.
## Resources
- [AirGap Step by Step Setup Guide](https://support.airgap.it/guides/step-by-step-guide/)
- [AirGap Vault Releases on GitHub](https://github.com/airgap-it/airgap-vault/releases)
- [AirGap Wallet Releases on GitHub](https://github.com/airgap-it/airgap-wallet/releases)
- [AirGap Wallet on Google Play Store](https://play.google.com/store/apps/details?id=it.airgap.wallet)
- [AirGap Vault on Google Play Store](https://play.google.com/store/apps/details?id=it.airgap.vault)
- [AirGap Wallet on Apple App Store](https://apps.apple.com/app/airgap-wallet/id1420996542)
- [AirGap Vault on Apple App Store](https://apps.apple.com/app/airgap-vault/id1417126841)
---
## Base Wallet
Base Wallet's smart wallet mode ("Base mode") is not yet supported on Acurast. If you have it enabled, you will need to disable it before connecting to the Acurast Hub.
## Error
When trying to connect with "Base mode" enabled, you will see the following error:
**Connection Failed** — *Please disable "Base mode" in the Wallet settings and try again.*
## How to disable Base mode
1. Open the Coinbase Wallet app and tap your **profile icon** (top-left).
2. Scroll down and toggle off **Base mode**.
3. Go back to [hub.acurast.com](https://hub.acurast.com/) and reconnect your wallet.
---
## Ledger Hardware Wallet (coming soon)
:::caution Ledger Support Not Yet Available
Ledger hardware wallets **cannot currently be used to sign Acurast transactions**. While it is possible to derive an Acurast address using a Ledger device, transaction signing is not yet supported. This means you cannot send tokens, stake, or interact with the Acurast network using a Ledger.
Full Ledger support is being worked on and will be announced once available.
:::
## Current Limitations
- **Transaction signing is not supported** — You cannot approve or submit any on-chain transactions (transfers, staking, etc.) using a Ledger device on Acurast.
- **Message signing only** — Ledger can currently only be used to sign messages, not transactions.
- **Do not transfer funds** to a Ledger-derived Acurast address until full transaction signing support is confirmed, as you will not be able to move them.
## What to Use Instead
In the meantime, you can use one of the following supported wallets to interact with Acurast:
- [SubWallet](/token-holders/wallets/wallets-subwallet)
- [Talisman](/token-holders/wallets/wallets-talisman)
- [AirGap](/token-holders/wallets/wallets-airgap)
---
## SubWallet
This guide explains how to create an address with [SubWallet](https://www.subwallet.app/). SubWallet is a comprehensive non-custodial wallet for Polkadot, Substrate, and Ethereum ecosystems.
:::info
SubWallet is the recommended wallet for Acurast due to its excellent support for Polkadot parachains and user-friendly interface. It's available as a browser extension and mobile app.
:::
## 1. Install SubWallet
### Browser Extension
1. Visit the official SubWallet website: [www.subwallet.app](https://www.subwallet.app/)
2. Click **"Download"** and select your browser (Chrome, Firefox, Brave, or Edge)
3. You'll be redirected to your browser's extension store
4. Click **"Add to [Browser]"** to install the extension
5. Pin the extension to your browser toolbar for easy access
### Mobile App
1. Download SubWallet from:
- **iOS**: [Apple App Store](https://apps.apple.com/us/app/subwallet-polkadot-wallet/id1633050285)
- **Android**: [Google Play Store](https://play.google.com/store/apps/details?id=app.subwallet.mobile)
2. Install and open the app
## 2. Create a New Wallet
When you first open SubWallet:
1. Click **"Create a new account"**
2. SubWallet will generate a recovery phrase (seed phrase) for you
3. **Write down your recovery phrase** and store it securely offline
4. Confirm your recovery phrase by selecting the words in the correct order
5. Create a strong master password for your wallet
6. Click **"Continue"** to complete the setup
:::danger IMPORTANT
Your recovery phrase is the master key to your wallet. Anyone with access to it can access your funds. Never share it, and keep multiple secure backups.
:::
## 3. Enable Acurast Network
To use Acurast with SubWallet, you need to enable the Acurast network:
1. Open the SubWallet extension or app
2. Click the **hamburger menu** (three horizontal lines) in the top left
3. Select **"Manage networks"** or **"Manage chains"**
4. In the search bar, type **"Acurast"**
5. Enable the Acurast network by toggling the switch:
- For Mainnet: Enable **"Acurast"** (coming soon)
- For Canary: Enable **"Acurast Canary"**
6. Close the network settings
Your SubWallet will now display your Acurast balance and allow you to interact with the Acurast network.
## 4. Get Your Acurast Address
To receive Acurast tokens, you'll need your Acurast address:
1. Open the SubWallet extension or app
2. Make sure you have the Acurast network enabled
3. Your main account view will show your balance
4. Click on the account name or address to view details
5. Click on your address or the **copy icon** to copy your Acurast address
6. Your address will be copied to the clipboard in the correct Acurast format
You can now share this address to receive ACU (Mainnet) or cACU (Canary) tokens.
## 5. Connect SubWallet to the Acurast Hub
To interact with the Acurast Hub using SubWallet:
1. Go to [hub.acurast.com](https://hub.acurast.com/) and click **Enter Hub**
2. Click **Connect**
3. Select **SubWallet** from the list of available wallets
4. In the SubWallet extension popup, review the connection request
5. Select the account(s) you want to connect to the hub
6. Click **Connect** or **Approve**
7. On the connection modal of the hub, your account will appear
8. Click on your account to enter the hub
You are now connected and can manage your Acurast deployments and processor operations.
## Troubleshooting
### SubWallet not connecting to Acurast Hub
- Make sure you have the Acurast network enabled in SubWallet
- Try refreshing the hub page
- Check that the SubWallet extension is unlocked
- Try to reconnect it to the hub, by clicking the connector icon next to your account
- Clear your browser cache and try again
### Balance not showing
- Verify that you've enabled the Acurast network in SubWallet settings
- Check that you're looking at the correct account
- Try switching networks and switching back to Acurast
- Wait a few moments for the network to sync
## Additional Resources
- [SubWallet Official Website](https://www.subwallet.app/)
- [SubWallet Documentation](https://docs.subwallet.app/)
- [SubWallet Support](https://docs.subwallet.app/main/support)
- [Acurast Hub](https://hub.acurast.com/)
---
## Talisman
This guide explains how to create an address with [Talisman](https://talisman.xyz/) wallet. To install the browser extensions, follow their setup instructions.
:::info
Most wallets allow you to create a generic polkadot address that is supported by Acurast, this guide focuses on the Talisman wallet.
:::
## 1. Create an Acurast address with Talisman
Once you created your recovery phrase follow these steps to create an Acurast address:
1. Open the settings menu (more) in your Talisman browser extension and select *Add Account*
2. Choose *New Polkadot Account*.
3. Select the Recovery Phrase to use and give your Account a name, eg "Acurast", then click *Create*
4. In order to display Acurast tokens, go to *Networks & Tokens* in the Settings menu and select *Manage Networks*. Then click *+ Add network*
5. Select *Polkadot* as platform, then enter the following data:
For Acurast Canary:
RPC Url: **`wss://public-rpc.canary.acurast.com`**
Native Token Symbol: **`cACU`**
Native Token Decimals: **`12`**
Native Token Name: **`cACU`**
Display Balances: **`Yes`**
6. Your Acurast Account on Talisman is now ready to be used and to sign transactions
## 2. Get the address of your Acurast account on Talisman
Open your Talisman browser extension and select the Acurast account. Then click on the copy&paste symbol on top right and your Acurast account address will be copied into the clipboard.
## 3. Connect your Talisman account to the Acurast Hub
1. Go to [hub.acurast.com](https://hub.acurast.com/) and click *Enter Hub*
2. Click *Connect*
3. Open your Talisman browser extension
4. On top of your Talisman extension, select the account you want to connect to hub.acurast.com
Select it and the bullet point will turn green:
5. Go back to the hub.acurast.com page and click *Talisman*. The account will now appear under *Select Account*. Click it
6. You are now connected to hub.acurast.com