# Conditional Transactions Source: https://docs.skale.space/concepts/bite-protocol/conditional-transactions Phase 2 of BITE - smart contracts that request decryption on-demand **Sandbox Access** - Conditional Transactions (Phase 2) is available in a Sandbox environment. Contact the SKALE team at [https://discord.gg/skale](https://discord.gg/skale) for access. ## What Are Conditional Transactions? Conditional Transactions (CTX) let smart contracts request decryption of encrypted data. Instead of decrypting everything at consensus time, contracts selectively reveal only what they need—when they need it. ## How It Works 1. **Submit Encrypted Data** - Client encrypts data and sends it to a smart contract 2. **Store Encrypted State** - Contract stores the encrypted bytes 3. **Request Decryption** - Contract calls the `submitCTX` precompile with encrypted and plaintext arguments 4. **Threshold Decrypt** - The validator committee decrypts the requested data 5. **Callback Execution** - The decrypted data is delivered to your contract's `onDecrypt` function ## The Smart Contract Flow Your contract implements `IBiteSupplicant`: ```solidity theme={null} import { BITE } from "@skalenetwork/bite-solidity/BITE.sol"; import { IBiteSupplicant } from "@skalenetwork/bite-solidity/interfaces/IBiteSupplicant.sol"; contract MyContract is IBiteSupplicant { address public ctxSender; function revealSecret(bytes calldata encrypted) external payable { bytes[] memory encryptedArgs = new bytes[](1); encryptedArgs[0] = encrypted; bytes[] memory plaintextArgs = new bytes[](0); // Create a Conditional Transaction ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, msg.value / tx.gasprice, encryptedArgs, plaintextArgs ); payable(ctxSender).sendValue(msg.value); } // Called by the network with decrypted data function onDecrypt( bytes[] calldata decryptedArgs, bytes[] calldata plaintextArgs ) external override { require(msg.sender == ctxSender, "Unauthorized"); // Use decryptedArgs[0] in your logic } } ``` ## Example: Confidential Game A simple game where encrypted numbers are decrypted and compared: ```solidity theme={null} contract ConfidentialGame is IBiteSupplicant { bytes[] public encryptedNumbers; bytes[] public plaintextNumbers; uint256 public sumDecrypted; address public ctxSender; bool public userWon; function submitEncrypted(bytes calldata encrypted) external { encryptedNumbers.push(encrypted); } function submitPlaintext(bytes calldata plaintext) external { plaintextNumbers.push(plaintext); } function decryptAndExecute() external payable { ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, msg.value / tx.gasprice, encryptedNumbers, plaintextNumbers ); } function onDecrypt( bytes[] calldata decryptedArgs, bytes[] calldata ) external override { require(msg.sender == ctxSender, "Unauthorized"); for (uint i = 0; i < decryptedArgs.length; i++) { sumDecrypted += uint256(bytes32(decryptedArgs[i])); } userWon = sumDecrypted < 1000; } } ``` ## Use Cases ### Confidential Auctions Bidders submit encrypted bids. The contract decrypts all bids after the deadline and determines the winner without revealing losing bids: ```solidity theme={null} function resolveAuction() external { // Decrypt all encrypted bids ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, 0, encryptedBids, new bytes[](0) ); } function onDecrypt(bytes[] calldata decryptedArgs, bytes[] calldata) external override { // Find highest bid and award item uint256 highestBid = 0; address winner; for (uint i = 0; i < decryptedArgs.length; i++) { (address bidder, uint256 amount) = abi.decode(decryptedArgs[i], (address, uint256)); if (amount > highestBid) { highestBid = amount; winner = bidder; } } // Award to winner (losing bids stay secret) } ``` ### Private Voting with Tallying Votes remain encrypted until tally time, preventing bribery and coercion: ```solidity theme={null} function tallyVotes() external { ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, 0, encryptedVotes, new bytes[](0) ); } function onDecrypt(bytes[] calldata decryptedArgs, bytes[] calldata) external override { // Tally all decrypted votes for (uint i = 0; i < decryptedArgs.length; i++) { uint256 proposalId = uint256(bytes32(decryptedArgs[i])); voteCounts[proposalId]++; } } ``` ### Confidential Oracle Data Oracles can submit encrypted data that only decrypts when a contract explicitly requests it: ```solidity theme={null} function updatePrice(bytes calldata encryptedPrice) external { latestEncryptedPrice = encryptedPrice; } function executeTrade() external { // Request decryption to execute trade ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, 0, new bytes[](1), new bytes[](0) ); } ``` ## Getting Started * [BITE API Reference](/developers/bite-protocol/bite-api-and-faqs) - Solidity helpers and `submitCTX` docs * [Conditional Transactions Cookbook](/cookbook/privacy/conditional-transactions) - Step-by-step tutorials * [Introduction](/concepts/bite-protocol/intro-bite-protocol) - BITE Protocol overview # Encrypted Transactions Source: https://docs.skale.space/concepts/bite-protocol/encrypted-transactions Phase 1 of BITE - encrypted mempool and MEV-resistant transactions **Live on SKALE Base** - Encrypted Transactions (Phase 1) is available on SKALE Base Mainnet and Testnet. ## What Are Encrypted Transactions? BITE's first phase hides the `to` address and `data` fields of transactions until after they execute. Once decrypted and executed, transaction details are visible on-chain like any other transaction. The encryption protects during mempool and consensus—before finality. After execution, the transaction is part of the immutable blockchain record and fully visible. ## How It Works 1. **Encrypt** - Wallet encrypts the transaction payload using SKALE's threshold public key 2. **Submit** - Encrypted transaction is sent to the BITE precompile (magic address) 3. **Finalize** - Consensus finalizes the encrypted transaction without seeing contents 4. **Decrypt** - After finality, the validator committee runs threshold decryption 5. **Execute** - Decrypted transaction executes in the EVM Because inclusion happens before decryption, SKALE achieves commit-then-reveal semantics at the protocol layer. ## MEV Resistance MEV bots need visible transaction data to extract value. By encrypting `to` and `data` during mempool and consensus, BITE removes the information they rely on: * **No front-running** - Transaction intent is hidden until execution * **No sandwich attacks** - Bots cannot see incoming swaps * **No censorship** - Validators cannot selectively filter based on content Combined with SKALE's zero gas fees and instant finality, encrypted transactions eliminate the primary MEV vectors that plague other chains. ## Use Cases ### Private Token Transfers Encrypt ERC20 transfers to hide amounts and recipients: ```typescript theme={null} import { BITE } from '@skalenetwork/bite'; const bite = new BITE(rpcEndpoint); const transferData = iface.encodeFunctionData('transfer', [recipient, amount]); const tx = { to: tokenAddress, data: transferData, gasLimit: 200000 }; const encryptedTx = await bite.encryptTransaction(tx); // Send encryptedTx - to and data are now hidden ``` ### Confidential Contract Interactions Keep function calls and parameters private: ```typescript theme={null} const callData = iface.encodeFunctionData('confidentialFunction', [secretParam]); const tx = { to: contractAddress, data: callData, gasLimit: 300000 }; const encryptedTx = await bite.encryptTransaction(tx); ``` ### Private Voting Encrypt votes to prevent bribery and coercion: ```typescript theme={null} const voteData = iface.encodeFunctionData('vote', [proposalId, support]); const tx = { to: governanceAddress, data: voteData, gasLimit: 100000 }; const encryptedTx = await bite.encryptTransaction(tx); ``` ### NFT Mint Privacy Hide metadata and mint details until reveal: ```typescript theme={null} const mintData = iface.encodeFunctionData('mint', [recipient, encryptedMetadata]); const tx = { to: nftAddress, data: mintData, gasLimit: 250000 }; const encryptedTx = await bite.encryptTransaction(tx); ``` ## Getting Started * [BITE API Reference](/developers/bite-protocol/bite-api-and-faqs) - TypeScript SDK and JSON-RPC methods * [Encrypted Transactions Cookbook](/cookbook/privacy/encrypted-transactions) - Step-by-step tutorials * [Introduction](/concepts/bite-protocol/intro-bite-protocol) - BITE Protocol overview # Introduction Source: https://docs.skale.space/concepts/bite-protocol/intro-bite-protocol BITE Protocol: encrypted transactions, MEV resistance, and confidential computing on SKALE ## What Is BITE? BITE (Blockchain Interoperability and Trustless Execution) adds encryption to SKALE while keeping full EVM compatibility. Transactions stay encrypted during mempool and consensus, then are decrypted by the validator committee for execution. Once executed, transaction details are visible on-chain like any other transaction. ## Why BITE? Public blockchains expose everything—transaction amounts, contract calls, user intent. This creates problems: * **MEV** - Bots see your transactions before they're confirmed and front-run them * **Privacy** - Sensitive business and personal data is visible to everyone * **Competition** - Competitors can monitor your contract interactions BITE solves these problems at the protocol layer without breaking EVM compatibility. ## How It Works BITE extends SKALE with threshold encryption built into consensus: 1. **Encrypt** - Your wallet encrypts the `to` and `data` fields using SKALE's threshold public key 2. **Submit** - Encrypted transaction goes to the BITE precompile (magic address) 3. **Finalize** - Consensus finalizes the encrypted transaction without seeing contents 4. **Decrypt** - After finality, the validator committee runs threshold decryption 5. **Execute** - Transaction executes normally in the EVM This is **commit-then-reveal** at the protocol level—inclusion happens before anyone can read the transaction. ## Key Properties ### MEV Resistance By encrypting `to` and `data`, BITE removes the information MEV bots need: * No front-running (intent hidden until finality) * No sandwich attacks (bots can't see swaps) * No censorship (validators can't filter by content) ### Threshold Decryption No single node can decrypt transactions. The validator committee jointly decrypts using threshold BLS—a minimum number of validators must collaborate. Keys rotate each epoch using onchain DKG. ### Zero Trust You don't need to trust any single party. The cryptography ensures: * No early decryption (requires threshold of validators) * No selective decryption (all or nothing per epoch) * No single point of failure (distributed key generation) ## EVM Compatible BITE works with existing tooling: * Metamask, WalletConnect, and other wallets * Foundry, Hardhat, and other development frameworks * Ethers.js, Viem, and other web3 libraries * Existing Solidity contracts (no changes needed for Phase 1) ## Phases BITE rolls out in four phases: | Phase | Name | Status | | ----- | ---------------------------- | ------------------ | | 1 | Encrypted Transactions | Live on SKALE Base | | 2 | Conditional Transactions | Sandbox | | 3 | Threshold Re-encryption | Roadmap | | 4 | Fully Homomorphic Encryption | Roadmap | See [Phases of BITE](/concepts/bite-protocol/phases) for the full roadmap. ## Learn More * [Phases of BITE](/concepts/bite-protocol/phases) - Evolution and phases of BITE Protocol * [Encrypted Transactions](/concepts/bite-protocol/encrypted-transactions) - Phase 1 details and use cases * [Conditional Transactions](/concepts/bite-protocol/conditional-transactions) - Phase 2 details and use cases # Phases of BITE Source: https://docs.skale.space/concepts/bite-protocol/phases Roadmap to encrypted-by-default SKALE Chains **Deployment Status** * **Phase 1: Encrypted Transactions** is live on SKALE Base Mainnet and Testnet * **Phase 2: Conditional Transactions** is available in a Sandbox environment — contact the SKALE team at [https://discord.gg/skale](https://discord.gg/skale) for access BITE rolls out in four phases, keeping SKALE EVM-compatible while adding privacy and MEV resistance. ## Phase 1: Encrypted Transactions Transactions are encrypted before entering the mempool, then decrypted by the validator committee after block finality. Transaction details remain hidden during mempool and consensus—preventing front-running and manipulation. Once executed, transactions are visible on-chain like any other. [Learn more →](/concepts/bite-protocol/encrypted-transactions) ## Phase 2: Conditional Transactions Smart contracts can request decryption of specific encrypted data on-demand. Contract state remains confidential throughout its lifecycle, with selective disclosure only when needed for execution. [Learn more →](/concepts/bite-protocol/conditional-transactions) ## Phase 3: Threshold Re-encryption Data can be re-encrypted with new keys without decryption, enabling fine-grained access control and dynamic privacy settings without exposing the underlying data. ## Phase 4: Threshold Fully Homomorphic Encryption (THFE) Computation on encrypted data becomes possible—smart contracts execute without revealing inputs or outputs, enabling privacy-preserving applications previously impossible on EVM chains. # Distributed Key Generation with BLS Source: https://docs.skale.space/concepts/dkg-with-bls How Distributed Key Generation (DKG) works on SKALE ## Overview Distributed Key Generation (DKG) lets a group of validators create a shared public key without any single member knowing the full private key. Each validator only holds a key share, but together they can produce threshold BLS signatures that SKALE uses for instant finality. DKG is the backbone of SKALE’s pooled security model. ## How SKALE’s DKG works 1. **Setup** – `n` validators participate with a tolerance of up to `t` malicious actors. 2. **Create polynomials** – Each validator creates a secret polynomial and publishes a public commitment to its coefficients on the elliptic curve. 3. **Share contributions** – Validators send each peer their private share (a point on the polynomial). Public commitments let everyone verify shares without revealing secrets. 4. **Verify and complain** – If a share is missing or invalid, the recipient files a complaint. SKALE Manager (on Ethereum) arbitrates, slashes bad actors, and restarts if needed—giving economic weight to honest behavior. 5. **Assemble keys** – Summing all public commitments yields the group public key; summing the received shares gives each validator its private key share. No single validator ever sees the full private key, but any supermajority can sign on behalf of the group. ## Verification and Complaints Validators check each received share against the sender’s public commitment on the curve. If the math doesn’t line up or a share never arrives, the receiver files a complaint. Instead of broadcasting questionable data to everyone, SKALE routes complaints through SKALE Manager on Ethereum. The contract verifies the evidence, slashes malicious validators, and can restart the ceremony with a clean committee. That onchain enforcement layer gives complaints real economic weight. ## Computing the public/private keys When all public commitments are summed, you get the group public key. When each validator sums the private shares they received, they get their private key share. Any `t + 1` shares can reconstruct the group private key, but SKALE keeps shares distributed so the full key is never materialized—only threshold signatures are produced. ## BLS Signatures ### Why BLS? BLS signatures are compact and aggregatable. They use pairing-based cryptography so individual signature shares combine cleanly into one signature that anyone can verify against the group public key. That makes them perfect for SKALE’s threshold finality. ### Signatures and Properties 1. Hash the message to a curve point. 2. Multiply by your private key share to create a signature share. 3. Combine signature shares from ≥2/3 of validators; thanks to pairings, they merge into one short signature. 4. Anyone verifies that single signature against the group public key. The result: one lightweight proof of supermajority agreement instead of dozens of individual signatures. ### Significance with DKG DKG supplies the group public key and each validator’s share; BLS lets those shares combine into a single signature when a supermajority participates. Because shares never reveal the full private key, SKALE gets instant finality and pooled security without concentrating trust. ### Polynomial Interpolation SKALE uses an `O(t^2)` Lagrange interpolation (a good fit for SKALE’s committee sizes) to combine signature shares in the exponent. It’s fast, doesn’t leak private information, and keeps validator hardware requirements modest. ## Known Attacks ### Joint-Feldman manipulation SKALE uses the Joint-Feldman variant for efficiency. Classic attacks try to bias randomness by provoking complaints and learning extra information. SKALE routes complaints through SKALE Manager, so disputed data is checked onchain and never leaked to peers—removing the information channel needed for biasing. ### Subgroup edge cases Adding points from the wrong subgroup could, in theory, produce an unusable public key if many malicious players collude. SKALE’s curve parameters and validator counts make this infeasible in practice; committees are far smaller than the smallest unwanted subgroup size. ### Front-running complaints Past vulnerability: a malicious node could submit data right after an honest complaint to get the complainer slashed. Fix: strict time slots for broadcasting and complaining plus onchain verification of evidence. Future threshold encryption further reduces this surface. # Intro to Elliptic Curve Cryptography Source: https://docs.skale.space/concepts/elliptic-curve-cryptography Introduction to Elliptic Curve Cryptography Elliptic curve cryptography (ECC) is the backbone of SKALE’s staking, consensus, and threshold signatures. Here’s the plain-English version of why ECC matters and how SKALE uses it. ## Why ECC matters for SKALE * **Strong security, small keys** – ECC delivers high security with compact keys and signatures, perfect for high-throughput chains. * **Foundation for staking** – Validator and delegator keys rely on elliptic-curve one-way functions. * **Threshold crypto** – BLS signatures and Distributed Key Generation (DKG) use elliptic curves to combine many shares into one verifiable signature. ## The math in approachable terms Elliptic curves define a special “addition” between points on the curve. Repeatedly “adding” a point to itself (scalar multiplication) is easy; reversing that process (finding how many times it was added) is hard. That hardness—the elliptic curve discrete logarithm problem (ECDLP)—is what keeps private keys private while letting everyone verify public keys. This one-way property lets SKALE nodes publish public keys while keeping private keys secret, so they can prove authorship of signatures without exposing the keys themselves. ## Signatures on SKALE: ECDSA vs. BLS * **ECDSA** – The familiar Ethereum-style signature. Great for wallets and user transactions; already supported by existing tooling. * **BLS (Boneh–Lynn–Shacham)** – Compact, aggregatable signatures that can be combined. SKALE uses BLS for threshold signatures so a committee of validators can produce one signature that proves supermajority agreement. Why BLS matters here: * Multiple validator signatures compress into one, saving bandwidth and verification time. * Threshold signatures enable instant finality once ≥2/3 of validators sign. * Works hand-in-hand with DKG so no single validator knows the full private key. ## Takeaways for builders * Keep using standard Ethereum wallets and tooling—ECC is already baked in. * BLS powers SKALE’s pooled security and instant finality; you get stronger guarantees without extra integration work. * If you need more detail on the crypto, see the DKG with BLS page for a deeper dive into how keys are generated and combined on SKALE. # Block Rotation Source: https://docs.skale.space/concepts/skale-chain/block-rotation How SKALE keeps nodes lightweight while preserving historical access Block rotation on a SKALE Chain limits the disk space used by consensus and full-sync nodes by pruning old blocks, ensuring efficient storage and performance. ## Storage Allocation Each SKALE Chain reserves **12.6 GB** of storage for core blockchain data on consensus and full-sync nodes. This includes: * `blocks` * `transactions` and their receipts * `log blooms` (for efficient event filtering) * `"best"` — latest block info (`lastBlockHash`) * `"chainStart"` — earliest available block (`firstBlockHash`, useful after importing snapshots) Block rotation does not apply to **SKALE Archive Nodes** or **SKALE Indexer Nodes**, which both store the full block history. ## How Block Rotation Works To stay within the 12.6 GB limit, SKALE Chains maintain a dynamic range of block data: * Only the **most recent 80–100%** of data is retained. * The **oldest 20%** is pruned as new blocks are added. * The rotation is based on **data size**, not block count. ### Example If block storage nears the 12.6 GB cap: * The chain deletes the oldest blocks until usage returns to \~80% of the cap. * Only recent blocks remain accessible on consensus and full-sync nodes. This sliding-window model ensures that the chain operates efficiently without growing indefinitely in size. ## Benefits * **Efficient resource use** — prevents storage bloat. * **High performance** — reduces read/write overhead. * **Scalability** — supports many lightweight SKALE Chains running in parallel. ## Full Historical Access If your app or service requires the entire blockchain history (e.g. for analytics, block explorers, or archival indexing), use a **SKALE Archive Node**, which: * Stores all past blocks * Maintains full transaction and log history * Is not affected by block rotation Want to run a SKALE Archive Node or build on top of historical data? Reach out on [SKALE's Discord](https://discord.gg/skale) or explore the [developer docs](/validators/run-archive-node). ## API Changes | Calls | Changes | | ----------------------------------------- | ------------------------------------------------ | | eth\_getBlockByNumber/eth\_getBlockByHash | may return null | | eth\_getBlockTransactionCountByNumber | may return null | | eth\_getBlockTransactionCountByHash | may return null | | eth\_getUncleCountByBlockNumber | may return null | | eth\_getUncleCountByBlockHash | may return null | | eth\_getTransactionByBlockHashAndIndex | may return null | | eth\_getTransactionByBlockNumberAndIndex | may return null | | eth\_getUncleByBlockHashAndIndex | may return null | | eth\_getUncleByBlockNumberAndIndex | may return null | | eth\_getTransactionByHash | may return null | | eth\_getTransactionReceipt | may return null | | eth\_getFilterLogs | will treat removed blocks as if they have 0 logs | | eth\_getLogs | will treat removed blocks as if they have 0 logs | # Consensus Source: https://docs.skale.space/concepts/skale-chain/consensus SKALE Mainnet Beta ## Protocol Overview A quick primer for product teams: SKALE uses leaderless, asynchronous BFT consensus with BLS threshold signatures. Blocks finalize instantly once 2/3 of a chain’s validator committee signs, so there are no reorgs or multi-confirmation waits. Randomized rotation and data-availability proofs keep proposals honest while maintaining high throughput and zero-gas UX. A SKALE chain is a Proof-of-Stake blockchain fully compatible with ETH mainnet. It can run ETH wallets, tools and dapps. As any Ethereum-compatible chain, SKALE chain includes a **blockchain**, which is a chain of transactions ordered into **committed blocks**, and a computing machine denoted as **EVM**. The set of variables stored in EVM is denoted as **EVM state**. **Transaction Processing Flow**: Transactions are processed sequentially through the EVM. Processed transactions (Tx1, Tx2, Tx3) flow through EVM processing, with each transaction being processed one at a time to update the EVM state. EVM processes committed blocks one transaction at a time. For each transaction it runs instructions (`bytecode`) specified by the transaction and changes EVM state. ### Architecture overview The purpose of SKALE chain is to order transactions into blocks and then process them by EVM. SKALE chain is composed of a fixed set of **N** network nodes that process user transactions in the following phases: * Accept and validate user transactions (*submission phase*) * Broadcast transactions to peer nodes (*broadcast phase*) * Store transactions into pending queues (*pending queue phase*) * Create block proposal for each block number and broadcast it to peers, collecting 2/3 N data availability signatures and creating DA proofs (*block proposal phase*) * Broadcast DA proofs to peers (*DA broadcast phase*) * Run block consensus for each block proposal to select a winning proposal (*block consensus phase*) * Sign the statement on which proposal won (block signature share) and broadcast it to other nodes. Wait until receipt of 2/3 of block signature shares and merge them into block signature (*block signature phase*) * Commit the winning proposal if node has it, otherwise download it from other nodes and commit it. The winning proposal becomes a committed block (*block finalization phase*) * Process the committed block through Ethereum Virtual Machine to transition to the new EVM state (*EVM processing phase*) * Store committed blocks and EVM state (*storage phase*) **Transaction Flow**: The typical transaction flow proceeds through the following phases: Transaction submission → Transaction broadcast → Pending Queue → Block Proposal → DA Broadcast → Block Consensus → Block Signature → Block Finalization → Block Commit → EVM Processing → Storage phase. Additionally, nodes can receive blocks through the Block Catchup mechanism, which connects directly to Block Commit. In addition to normal block processing, a node can receive blocks through **block catchup** mechanism. **Block catchup** means that the node does not participate in block consensus. Instead, it simply downloads committed blocks from other nodes, verifying block signatures. Block catchup typically happens when a node is powered on after being offline. Block catchup can also be used by third-party nodes that do not participate in core chain, such as archive nodes. Block consensus and block catchup run in parallel. This means that every node in addition to normal block consensus procedure makes periodic random connections to other nodes, to attempt to download ready committed blocks. The blockchain provides a *guarantee that every transaction is included into the chain only once*. This means, in particular, that when a node commits a block, the node will remove the transactions included in the block from the pending transaction queue. ### SKALE node overview Each node runs **skaled**, SKALE software blockchain agent. **skaled** is composed of: * **Network API module** accepts transactions and user requests * **Transaction validation module** validates transactions on receipt * **Pending queue module** holds transactions * **Transaction broadcast module** broadcasts valid transactions to other nodes in the chain * **Proposal module** creates block proposals for consensus * **Proposal broadcast module** broadcasts block proposals to peers and collects DA proofs * **DA proof broadcast module** broadcasts DA proofs to peers * **Consensus module** selects the winning block proposal and turns it into a committed block, and then creates block signature by assembling signature shares * **Finalization module** downloads winning proposals from other nodes, if a node does not have a copy of winning proposal by completion of block consensus * **EVM module** processes the committed block * **Block storage module** stores committed blocks, deleting old blocks if skaled runs out of block storage space (block rotation) * **State storage module** stores EVM state. State information is *never deleted automatically*. Cleaning up the state is the responsibility of dapps **SKALE Node Architecture**: The Network API Module receives transactions and passes them to the Transaction validation module. Validated transactions flow to both the Pending queue module and the Transaction Broadcast Module. The Pending queue module sends block proposals to the Proposal Module, which receives DA Proofs and sends them to the DA proof broadcast module. Proposals and DA proofs flow to the Consensus module, which reaches consensus on the winning proposal and sends it to the Finalization module. The Finalization module sends the committed block to the EVM module, which processes it and sends committed blocks to the Block Storage Module and EVM state to the EVM Storage module. ### Security assumptions overview SKALE is *provably secure*. This means one can prove two qualities of the blockchain: * **Consistency** - for any block number, committed blocks and EVM state are identical on each node. Note that due to network delays, some nodes may at a given moment have less committed blocks than others. Therefore, the consistency is eventual. * **Liveliness** - the blockchain will always keep producing new committed blocks. Provable security means that *under certain mathematical assumptions*, SKALE chain *will always be consistent and lively, no matter what the attacker does*. The mathematical assumptions for provable security are specified below. #### Node security assumptions We assume that out of **N** nodes, **t** nodes at maximum are Byzantine (malicious), where $3t + 1 \leq N$ Simply speaking, not more than 1/3 of nodes can be malicious. For instance, if N = 16, the maximum number of malicious nodes is 5. The identity of malicious nodes is not known. A malicious node will typically pretend being an honest node. A malicious node will attempt to break the consistency and liveliness of the network by sending malicious messages, or not sending any messages when it is supposed to send a message by a protocol. It is assumed that malicious nodes do not control network routers and links. This means, in particular, that malicious nodes cannot affect messages sent between honest nodes, such as corrupting or reordering them. #### Network security assumptions The algorithms used by SKALE make assumptions about *the properties of the underlying network*. SKALE assumes that *the network is asynchronous and reliable with eventual delivery guarantee*. This means that: * Nodes are assumed to be connected by *reliable communications links* * Links can be arbitrarily slow, but will eventually deliver messages The asynchronous model described above is *similar to the model assumed by Bitcoin and Ethereum blockchains*. It reflects the state of modern Internet, where temporary network splits and interruptions are normal, but always resolve eventually. Since real Internet sometimes drops messages on the way without delivering them, *the eventual delivery guarantee is achieved in practice by retransmissions*. The sending node will make *multiple attempts to transfer* a message to the receiving node, until the transfer is successful and is confirmed by the receiving node. ### Protocol phases overview #### Submission phase During submission phase a user client (browser or mobile app) signs a transaction using user private wallet key and submits it either directly to one of core nodes or to a network proxy. A network proxy is a node that load balances incoming transactions to core nodes attempting to load them evenly, and avoiding transaction submissions to non-responsive nodes. #### Broadcast phase During the broadcast phase, a node that received a transaction from user client will broadcast it to other core nodes. #### Pending queue phase During the pending queue phase, a transaction received from user client or from transaction broadcast is validated and placed into the pending queue. During the validation, transaction signature and format are verified. The pending queue has fixed memory capacity. If the pending queue is full, adding a new transaction to the queue will cause some transactions to be dropped from the pending queue. Ethereum-compatible blockchains, including SKALE, drop transactions with the smallest gas price. #### Block proposal phase During the block proposal phase each SKALE node will form a block proposal. A block proposal is an ordered list of transactions. If all transactions in pending queue can be placed into proposal without reaching block gas limit, then all transactions will be placed into block proposal. Otherwise, transactions with higher gas price will be selected from the queue to create a block proposal that fits the block gas limit. Once a node created a proposal, it will broadcast compressed proposal to all its nodes. The compressed proposal includes only the transaction hash (fingerprint) of each transaction. The receiving node decompresses transactions by matching transaction hashes to transactions stored in its pending queue. In the event receiving node does not have a matching transaction in its pending queue, it will ask the sending node for the entire transaction. Once the receiving node receives the block proposal, it will sign a Data Availability Signature and pass it to the sending node. Once the sending node collects DA signatures from 2/3 of nodes, it will merge the signatures into a DA proof. The DA proof proves that the proposal has been widely distributed over the network. #### DA broadcast phase Once a node obtains a DA proof for its block proposal, it will broadcast DA proof to other nodes. The DA proof requirement solves two problems: 1. A block proposal that has a DA proof is *guaranteed to be widely distributed*. 2. Since DA proof creation requires a 2/3 signature of nodes, the proposal is *guaranteed to be unique*. A malicious proposal is not able to create two different proposals and obtain DA proofs for both of them. #### Block consensus phase Once a node receives DA proofs from 2/3 of nodes, the node will start the block consensus phase. During block consensus phase, the node will vote `1` if it received DA proof for a particular proposal, and vote `0` otherwise. The nodes will then execute asynchronous binary consensus algorithm, also known as Byzantine Generals problem. See [Byzantine fault tolerance](https://en.wikipedia.org/wiki/Byzantine_fault) for more information. The particular binary consensus algorithm implemented in SKALE is specified in [this paper](https://inria.hal.science/hal-00944019/file/RR-2016-Consensus-optimal-V5.pdf). Once the binary consensus completed, it guarantees that all honest nodes will reach consensus of `1` or `0`. If honest nodes reach `1` it is guaranteed that `1` was initially voted by at least one honest node. That, in turn, guarantees that the block proposal is DA safe, or that it is widely distributed over the network. If a block consensus phase outputs `1` for several proposals, the proposal with highest priority is selected. The priority changes from one block to another so that on average each node has similar probability to win. #### Block signature phase After block consensus decides on the winning block, each node will sign the statement specifying the winning proposal (block signature share) and broadcast it to other nodes. The node will then wait until receipt of 2/3 of block signature shares and merge the shares into block signature. #### Block finalization phase On completion of *block signature phase*, all honest nodes will have the block signature but some of them may not have the block itself. This can happen due to a malicious proposer, that intentionally does not send its proposals to some of the all nodes in order to break the liveliness property of the blockchain. It can also happen due to proposer crashing, or due to slow network. Fortunately, DA proof requirement solves the problem. It is guaranteed, that block proposal that wins *block consensus phase* has DA proof, and is, therefore, widely distributed across the network. Therefore, during *block finalization phase* if a node does not happen to have the winning proposal, it will simply connect to other nodes to download it from them. 2/3 of the nodes are guaranteed to have a copy of the proposal after DA proof phase. #### EVM processing phase After block finalization the block is present on the node. It will be then processed through Ethereum Virtual Machine to update EVM state. #### Storage phase Committed block will now be stored in persistent storage, and EVM state will be updated in persistent storage. The node will move into *block proposal phase* for the next block. ### Achieving eventual delivery by retransmissions Since real Internet sometimes drops messages on the way without delivering them, *the eventual delivery guarantee is achieved in practice by retransmissions*. The sending node will make *multiple attempts to transfer* a message to the receiving node, until the transfer is successful and is confirmed by the receiving node. Each sending node maintains a separate outgoing message queue for each receiving node. To schedule a message for delivery to a particular node, message is placed into the corresponding outgoing message queue. Each outgoing message queue is serviced by a separate program thread. The thread reads messages from the queue and attempts to transfer them to the destination node. If the destination node temporarily does not accept messages, the thread will keep initiating transfer attempts until the message is delivered. The destination node can, therefore, temporarily go offline without causing messages to be lost. Since there is a dedicated message sending thread for each destination node, messages are sent independently. Failure of a particular destination node to accept messages will not affect receipt of messages by other nodes. In the remainder of this document, anywhere where it is specified that a message is sent from node A to B, we mean reliable independent delivery as described above. ### Consensus state Each node stores *consensus state*. For each round of consensus, consensus state includes the set of proposed blocks, as well as the state variables of the protocols used by the consensus round. The state is stored in non-volatile memory and preserved across reboots. ### Reboots and crashes During a reboot, a node will temporarily become unavailable. After a reboot, messages destined to the node will be delivered to the node. Therefore, a reboot does not disrupt operation of asynchronous consensus. Since consensus protocol state is not lost during a reboot, a node reboot will be interpreted by its peers as a temporarily slowdown of network links connected to the node. A **hard crash** is an event where a node loses all or parts of the consensus state. For instance, a node can lose received block proposals or values of protocol variables. A hard crash can happen in case of a software bug or a hardware failure. It also can happen if a node stays offline for a very long time. In this case, the outgoing message queues of nodes sending messages to this node will overflow, and the nodes will start dropping older messages. This will lead to a loss of a protocol state. ### Default queue lifetime This specification specifies one hour as a default lifetime of a message which has been placed into an outgoing queue. Messages older than one hour may be dropped from the message queues. A reboot, which took less than an hour is, therefore, guaranteed to be a normal reboot. ### Limited hard crashes Hard crashes are permitted by the consensus protocol, as long as not too many nodes crash at the same time. Since a crashed node does not conform to the consensus protocol, it counts as a Byzantine node for the consensus round, in which the state was lost. Therefore, only a limited number of concurrent hard crashes can exist at a given moment in time. The sum of crashed nodes and Byzantine nodes cannot be more than **t** in the equation above. Then the crash is qualified as a limited hard crash. During a limited hard crash, other nodes continue block generation and consensus. The blockchain continues to grow. When a crashed node is back online, it will sync its blockchain with other nodes using a catchup procedure described in this document, and start participating in consensus. ### Widespread crashes A widespread crash is a crash where the sum of crashed nodes and Byzantine nodes is more than **t**. During a *widespread crash* a large proportion of nodes or all nodes may lose the state for a particular round and consensus progress may stall. The blockchain, therefore, may lose its liveliness. Security of the blockchain will be preserved, since adding a new block to blockchain requires a supermajority threshold signature of nodes, as described later in this document. The simplest example of a widespread crash is when more than 1/3 of nodes are powered off. In this case, consensus will stall. When the nodes are back online, consensus will start working again. In real life, a widespread crash can happen due to a software bug affecting a large proportion of nodes. As an example, after a software update all nodes in an SKALE Chain may experience the same bug. ### Failure resolution protocol In a case of a catastrophic failure a separate failure resolution protocol is used to restart consensus. First, nodes will detect a catastrophic failure by detecting absence of new block commits for a long time. Second, nodes will execute a failure recovery protocol that utilizes Ethereum main chain for coordination. Each node will stop consensus operation. The nodes will then sync their blockchains replicas, and agree on time to restart consensus. Finally, after a period of mandatory silence, nodes will start consensus at an agreed time point in the future. ### Blockchain architecture Each node stores a sequence of blocks. Blocks are constructed from transactions submitted by users. The following properties are guaranteed: * **Block sequence** - each node stores a block sequence B\_i that have positive block IDs ranging from 0 to HEAD * **Genesis block** - every node has the same genesis block that has zero block id * **Liveliness** - the blockchain on each node will continuously grow by appending newly committed blocks. If users do not submit transactions to the blockchain, empty blocks will be periodically committed. Periodic generation of empty blocks serves as a beacon to monitor liveliness of the blockchain * **Fork-free consistency** - due to network propagation delays, blockchain lengths on two nodes A and B may be different. For a given block id, if both node A and node B possess a copy of a block, the two copies are guaranteed to be identical ### Honest and Byzantine Nodes An **honest node** is a node that behaves according to the rules described in this document. A **Byzantine node** can behave in arbitrary way, including doing nothing at all. The goal of a Byzantine node is to either violate the liveliness property of the protocol by preventing the blockchain from committing new blocks or violate the consistency property of the protocol by making two different nodes commit two different blocks having the same block ID. It is assumed that out of **N** total nodes, **t** nodes are Byzantine, where the following condition is satisfied: $3t + 1 \leq N$ or $t \leq floor((N-1)/3)$ The above condition is well known in the consensus theory. There is a proof that shows that secure asynchronous consensus is impossible for larger values of **t**. It is easy to show that if a security proof works for a certain number of Byzantine nodes, it will work for fewer Byzantine nodes. Indeed, an honest node can always be viewed as a Byzantine node that decided to behave honestly. Therefore, in proofs, we always assume that the system has the maximum allowed number of Byzantine nodes: $t \leq floor((N-1)/3)$ In this case the number of honest nodes is: $h = N - t = (2N+1)/3$ Note, that it is beneficial to select **N** in such a way that (N-1)/3 is divisible by 3. Otherwise an increase in **N** does not lead to an increase in the maximum allowed number of Byzantine nodes. As an example, for N = 17 we get t = 5, so an increase in **N** does not improve Byzantine tolerance. In this specification, we assume that **N** is always selected in such a way that (N-1) is divisible by 3. In this case, expressions simplify as follows: $t \leq floor((N-1)/3)$ $h = (2N+1)/3$ ### Mathematical properties of node voting Consensus uses voting rounds. It is, therefore, important to prove some basic mathematical properties of voting. Typically, a node will vote by signing a value and transmitting it to other nodes. To count votes, a receiving node will count received signatures for a particular value **v**. The number of Byzantine nodes is less than a simple majority of honest nodes. This directly follows from the fact that: $h = 2t + 1$ and, therefore, a simple majority of honest nodes is: $s = t + 1$ We define *supermajority* as a vote of at least: $(2N+1)/3$ nodes. *A vote of all honest nodes is a supermajority*. Proof: this comes from the fact that: $h = (2N+1)/3$ If a particular message was signed by a supermajority vote, at least a simple majority of honest nodes signed this message. Even if all Byzantine nodes participate in a supermajority vote, the number of honest votes it needs to receive is: $(2N+1)/3 - t = 2t + 1 - t = t + 1$ which is exactly the simple majority of honest nodes **s**. If honest nodes are required to never sign conflicting messages, two conflicting messages cannot be signed by a supermajority vote. Proof: let **A** and **B** be two conflicting messages. Since a particular honest node will sign either **A** or **B**, both **A** and **B** cannot get simple majority of honest nodes. Since a supermajority vote requires participation of a simple majority of honest nodes, both **A** and **B** cannot reach a supermajority, even if Byzantine nodes vote for both. A supermajority vote, is, therefore, an important conflict avoidance mechanism. If a message is signed by a supermajority vote, it is guaranteed that no conflicting messages exist. As an example, if a block is signed by a supermajority vote, it is guaranteed that no other block with the same block ID exists. ### Threshold signatures Our protocol uses threshold signatures for supermajority voting. Each node is supposed to be in possession of BLS private key share PKS\_I. Initial generation of key shares is performed using joint-Feldman Distributed Key Generation (DKG) algorithm that is described in this document. DKG algorithm is executed when an SKALE Chain is created. Nodes are able to collectively issue supermajority threshold signatures on messages, where the threshold value is equal to the supermajority vote: $(2N+1)/3$ For instance for N = 16, the threshold value is 11. BLS threshold signatures are implemented as described in the paper by Boldyreva. BLS threshold signatures require a choice of elliptic curve and group pairing. We use elliptic curve (altBN256) and group pairing (optimal-Ate) implemented in Ethereum Constantinople release. To verify the signature, one uses BLS public key **PK**. This key is computed during the initial DKG algorithm execution. The key is stored in SKALE manager contract on Ethereum mainnet and is available to anyone. ### Transactions Each user transaction **T** is assumed to be an Ethereum-compatible transaction, represented as a sequence of bytes. ### Block format: header and body Each block is a byte string, which includes a header followed by a body. ### Block format: header Block header is a JSON object that includes the following: * **BLOCK\_ID** - integer id of the current block, starting from 0 and incremented by 1 * **BLOCK PROPOSER** - integer id of the node that proposed the block * **PREVIOUS BLOCK HASH** - SHA-3 hash of the previous block * **CURRENT BLOCK HASH** - the hash of the current block * **TRANSACTION COUNT** - count of transactions in the current block * **TRANSACTION SIZES** - an array of transaction sizes in the current block * **CURRENT BLOCK PROPOSER SIG** - ECDSA signature of the proposer of the current block * **CURRENT BLOCK TSIG** - BLS supermajority threshold signature of the current block All integers in this spec are unsigned 64-bit integers unless specified otherwise. ### Block format: body **BLOCK BODY** is a concatenated transactions array of all transactions in the block. ### Block format: hash Block hash is calculated by taking 256-bit Keccak hash of block header concatenated with block body, while omitting CURRENT BLOCK HASH, CURRENT BLOCK SIG, and CURRENT BLOCK TSIG from the header. The reason why these fields are omitted is because they are not known at the time block is hashed and signed. Throughout this spec we use SHA-3 as a secure hash algorithm. ### Block verification A node or a third party can verify the block by verifying a threshold signature on it and also verifying the previous block hash stored in the block. Since the threshold signature is a supermajority threshold signature and since any honest node will only sign a single block at a particular block ID, no two blocks with the same block ID can get a threshold signature. This provides security against forks. ### Block proposal format A block starts as a block proposal. A block proposal has the same structure as a block, but has the threshold signature element unset. Nodes concurrently make proposals for a given block ID. A node can only make one block proposal for a given block ID. Once a block proposal is selected to become a block by consensus, it is signed by a supermajority of nodes. A signed proposal is then committed to the end of the chain on each node. ### Pending transactions queue Each node will keep a pending transactions queue. The first node that receives a transaction will attempt to propagate it to all other nodes in the queue. A user client software may also directly submit the transaction to all nodes. When a node commits a block to its blockchain, it will remove the matching transactions from the transaction queue. ### Gas fees Each transaction requires payment of a gas fee, compatible with ETH gas fee. The gas fee can be paid in native currency of the SKALE chain (sFUEL) or in Proof of Work. The gas price is adjusted after each committed block. It is decreased if the block has been underloaded, meaning that the number of transactions in the block is less than 70 percent of the maximum number of transactions per block, and is increased if the block has been overloaded. ### Compressed block proposal communication Typically pending queues of all nodes will have similar sets of messages, with small differences due to network propagation times. When node **A** needs to send to node **B** a block proposal **P**, **A** does not need to send the actual transactions that compose **P**. **A** only needs to send transaction hashes, and then **B** will reconstruct the proposal from hashes by matching hashes to messages in its pending queue. In particular, for each transaction hash in the block proposal, the receiving node will match the hash to a transaction in its pending queue. Then, for transactions not found in the pending queue, the receiving node will send a request to the sending node. The sending node will then send the bodies of these transactions to the receiving node. After that the receiving node will then reconstruct the block proposal. ## Consensus data structures and operation ### Blockchain For a particular node, the blockchain consists of a range of committed blocks B\_i starting from B\_0 and ending with B\_TIPID, where TIP\_ID is the ID of the largest known committed block. Block ids are sequential positive integers. Blocks are stored in non-volatile storage. ### Consensus rounds New blocks are created by running consensus rounds. Each round corresponds to a particular **BLOCK\_ID**. At the beginning of a consensus round, each node makes a block proposal. When a consensus round completes for a particular block, one of block proposals wins and is signed using a supermajority signature, becoming a committed block. Due to a randomized nature of consensus, there is a small probability that consensus will agree on an empty block instead of agreeing on any of the proposed blocks. In this case, an empty block is pre-committed to a blockchain. ### Catchup agent There are two ways, in which blockchain on a particular node grows and TIP\_ID is incremented: **Normal consensus operation**: during normal consensus, a node constantly participates in consensus rounds, making block proposals and then committing the block after the consensus round commits. **Catchup**: a separate catchup agent is continuously running on a node. The catchup engine is continuously making random sync connections to other nodes. During a sync both nodes sync their blockchains and block proposal databases. If during catchup, node **A** discovers that node **B** has a larger value of TIP\_ID, **A** will download the missing blocks range from **B**, and commit it to its chain after verifying supermajority threshold signatures on the received blocks. Both normal and catchup operation append blocks to the blockchain. The catchup procedure is intended to catchup after hard crashes. When the node comes online from a hard crash, it will immediately start participating in the consensus for new blocks by accepting block proposals and voting according to consensus mechanism, but without issuing its own block proposals. Since a block proposal requires hash of the previous block, a node will only issue its own block proposal for a particular block id once the catchup procedure moves the TIP\_ID to a given block id. Liveliness property is guaranteed under hard crashes if the following is true: normal consensus guarantees liveliness properly, catch-up algorithm guarantees eventual catchup, and if the number of nodes in a hard crashed state at a given time plus the number of Byzantine nodes is less or equal to N/3. Since the normal consensus algorithm is resilient to having: $(N-1)/3$ Byzantine nodes, normal consensus will still proceed if we count crashed nodes as Byzantine nodes and guarantee that the total number of Byzantine nodes is less than: $(N-1)/3$ When a node that crashed joins the system back, it will immediately start participating in the new consensus rounds. For the consensus rounds that it missed, it will use the catchup procedure to download blocks from other nodes. ## Normal consensus operation ### Block proposal creation trigger A node is required to create a block proposal directly after its TIP\_ID moves to a new value. TIP\_ID will be incremented by 1 once a previous consensus round completes. TIP\_ID will also move, if the catchup agent appends blocks to the blockchain. ### Block proposal creation algorithm To create a block a node will: 1. Examine its pending queue 2. If the total size of transactions in the pending queue TOTAL\_SIZE is less or equal than MAX\_BLOCK\_SIZE, fill in a block proposal by taking all transactions from the queue 3. Otherwise, fill in a block proposal of MAX\_BLOCK\_SIZE by taking transactions from oldest received to newest received 4. Assemble transactions into a block proposal, ordering transactions by sha-3 hash from smallest value to largest value 5. In case the pending queue is empty, the node will wait for BEACON\_TIME and then, if the queue is still empty, make an empty block proposal containing no transactions The node does not remove transactions from the pending queue at the time of proposal. The reason for this is that at the proposal time there is no guarantee that the proposal will be accepted. **MAX\_BLOCK\_SIZE** is the maximum size of the block body in bytes. Currently we use MAX\_BLOCK\_SIZE = 8 MB. FUTURE: We may consider self-adjusting block size to target a particular average block commit time, such as 1s. **BEACON\_TIME** is time between empty block creation. If no-one is submitting transactions to the blockchain, empty beacon blocks will be created. Beacon blocks are used to detect normal operation of the blockchain. The current value of BEACON\_TIME is 3s. ### Block proposal reliable communication algorithm Once a node creates a block proposal it will communicate it to other nodes using the data availability protocol described below. The data availability protocol guarantees that if the protocol completes successfully, the message is transferred to the supermajority of nodes. The five-step protocol is described below: 1. **Step 1**: the sending node **A** sends the proposal **P** to all of its peers 2. **Step 2**: each peer on receipt of **P** adds the proposal to its proposal storage database PD 3. **Step 3**: the peer then sends a receipt back to **A** that contains a threshold signature share for **P** 4. **Step 4**: **A** will wait until it collects signature shares from a supermajority of nodes (including itself). **A** will then create a supermajority signature **S**. This signature serves as a receipt that a supermajority of nodes are in possession of **P** 5. **Step 5**: **A** will send the supermajority signature to each of the nodes **Data Availability Receipt Requirement**: In further consensus steps, any node voting for proposal **P** is required to include **S** in the vote. Honest nodes will ignore all votes that do not include the supermajority signature **S**. The protocol used above guarantees data availability, meaning that any proposal **P** that wins consensus will be available to any honest nodes. This is proven in steps below. **Liveliness**: If **A** is honest, then the five-step protocol above will always complete. By completion of the protocol we mean that all honest nodes will receive **S**. Byzantine nodes will not be able to stall the protocol. By properties of the send operation discussed above, all sends in Step 1-3 are performed in parallel. In step 4 node **A** waits to receive signature shares for the supermajority of nodes. This step will always complete in finite time, even if Byzantine nodes do not reply. This comes from the fact that there is a supermajority of honest nodes. In step 5 **S** will be added to outgoing message queues of all nodes. Since honest nodes do accept messages, **S** will ultimately be delivered to all honest nodes. If a proposal has a supermajority signature, it was communicated to and stored on the simple majority of honest nodes. The proof directly follows from the fact that an honest node **B** only signs the proposal after **B** has received and stored the proposal. If a proposal wins consensus and is to be committed to the blockchain, then any honest node **X** that does not have the proposal can efficiently retrieve it. First, a proposal will not pass consensus without having a supermajority signature. This comes from the fact that all nodes voting for the proposal will need to include **S** in the vote. By the properties of binary Byzantine agreement protocol of Mostéfaoui et al., a proposal can win consensus only if at least one honest node votes for the proposal. A proposal without a signature will never win consensus, since an honest node will never vote for it. Therefore, if a proposal won consensus, it is guaranteed to have a supermajority signature. Second, by previous lemma, if a proposal has a supermajority signature, any honest node can retrieve it. This completes the proof. The protocol discussed above is important because it guarantees that if a proposal wins consensus, all honest nodes can get this proposal from other honest nodes and add it to the blockchain. ### Pluggable Binary Byzantine Agreement The consensus described above uses an Asynchronous Binary Byzantine Agreement (ABBA) protocol. We currently use ABBA from Mostéfaoui et al. Any other ABBA protocol **P** can be used, as long as it has the following properties: * **Network model**: **P** assumes asynchronous network messaging model described above * **Byzantine nodes**: **P** assumes less than one third of Byzantine nodes, as described by the equation above * **Initial vote**: **P** assumes that each node makes an initial vote `yes(1)` or `no(0)` * **Consensus vote**: **P** terminates with a consensus vote of either `yes` or `no`, where if the consensus vote is `yes`, it is guaranteed that at least one honest node voted yes An ABBA protocol typically outputs a random number COMMON\_COIN as a byproduct of its operation. We use this COMMON\_COIN as a random number source. ### Consensus round A consensus round **R** is executed for each **BLOCK\_ID** and has the following properties: * For each **R** nodes will execute **N** instances of ABBA * Each ABBA\_i corresponds to a vote on block proposal from the node **i** * Each ABBA\_i completes with a consensus vote of `yes` or `no` * Once all ABBA\_i complete, there is a vote vector v\_i, which includes `yes` or `no` for each proposal * If there is only one `yes` vote, the corresponding block proposal **P** is committed to the blockchain * If there are multiple `yes` votes, **P** is pseudo-randomly picked from the `yes`-voted proposals using pseudo-random number **R**. The winning proposal index is the remainder of division of **R** by n\_win, where n\_win is the total number of `yes` proposals * The random number **R** is the sum of all ABBA COMMON\_COIN * In the rare case when all votes are `no`, an empty block is committed to the blockchain. The probability of an all-no vote is very small and decreases when **N** increases **Liveliness**: each consensus round **R** will always produce a block in a finite time. The proof follows from the fact that each **R** runs **N** parallel versions of ABBA binary consensus, and from the liveliness property of the ABBA consensus. **Consistency**: each consensus round will produce the same result **P** on all nodes. This follows from the consistency property of the ABBA consensus and from the fact that the consensus round algorithm is deterministic and does not depend on the node where it is executed. **Data Availability**: the winning proposal **P** is available to any honest node. This follows from the fact that ABBA will not return consensus `yes` vote unless at least one honest node initially votes `yes`, and from the fact that an honest node will not vote `yes` unless it has a data availability proof (threshold signature **S**). ## Consensus round vote trigger Each node **A** will vote for ABBAs in a consensus round **R** immediately after proposal phase completes, meaning that two processes complete: 1. **A** receives a supermajority of block proposals for this round, including data availability signatures 2. **A** transmits its block proposal to a supermajority of nodes **Liveliness**: the block proposal phase will complete in finite time, and the node will proceed with voting. Indeed, since a supermajority of nodes are honest, and since every honest node sends its block proposal and data availability signature to all other nodes, at some point in time **A** will receive proposals and data availability signatures from a supermajority of nodes. Also, since a supermajority of destination nodes are honest, at some point in time the node will transmit its block proposal to a supermajority of nodes. It will vote `yes` for each block proposal that it received, and `no` for each block proposal that it did not receive. Vote of each honest node will include: $(2N+1)/3$ `yes` votes and: $(2N-1)/3$ `no` votes. This simply follows from the fact that node **A** votes immediately after receiving a supermajority of block proposals, and from the fact that **A** votes yes for each block proposal that it received. ## Finalizing Winning Block Proposal Once consensus completes on a particular node **A** and the winning block proposal, the node will execute the following algorithm to finalize the proposal and commit it to the chain: 1. **A** will check if it has received the winning proposal **P** 2. If **A** has not received the proposal, it will download it from its peer nodes using the algorithm described later in this document. It is possible to do it because of the data availability guarantee 3. **A** will then sign a signature share for **P** and send it to all other nodes 4. **A** will then wait to receive signature shares from a supermajority of nodes, including itself 5. Once **A** has received a supermajority of signature shares, it will combine them into a threshold signature 6. **A** will then commit the **P** to the blockchain together with the threshold signature of **P** The proposal download algorithm is specified below. The proposal assumes that the proposal is split in N-1 chunks of equal size: $ceil(size(P)/(N-1))$ except the last chunk the size of which will be the remainder of: $size(P)/(N-1)$ The purpose of the algorithm is to minimize network traffic: 1. **A** sends a message to each peer **i**, requesting for chunk **i** 2. **A** waits until it receives a supermajority - 1 of responses 3. **A** then enumerates missing chunks 4. **A** then randomly assigns each missing chunk to servers, and empty chunks to each server that did not get a missing chunk assigned, and sends the corresponding requests to each server 5. **A** waits until receives supermajority - 1 of responses 6. If **A** received all chunks, the algorithm is complete. Otherwise it goes back to step 3 FUTURE: we may implement more advanced algorithms based on erasure codes. ### Purging old transactions For each node, 33 percent of the storage is assigned to blockchain, 33 percent to EVM and 33 to the rest of the system, such as consensus state. If blockchain storage is exhausted, the old blocks will be deleted to free storage in increments of 1024 blocks. If EVM/Solidity storage is exhausted, EVM will start throwing "OutOfStorage" errors until storage is freed. If consensus storage is exhausted, the consensus agent will start erasing items such as messages in the message outgoing queues, in the order of item age, from oldest to newest. ## EVM/Solidity ### EVM compatibility The goal is to provide EVM/Solidity compatibility, except the cases documented in this specification. The compatibility is for client software, in particular Metamask, Truffle, Web3js and Web3py. ### EVM execution Once a block is finalized on the chain, it is passed to EVM, and each transaction is sequentially executed by the EVM one after another. We currently use unmodified Ethereum EVM, therefore there should not be compatibility issues. Once Ethereum finalizes EWASM version of EVM, we will be able to plug it in. ### EVM storage EVM has pluggable storage backend database to store EVM/Solidity variables. We simplified and sped up the storage by using LevelDB from Google. Each variable in EVM is stored as a key value in LevelDB where the key is the sha3 hash of the virtual memory address and the value is the 256 bit value of the variable. In EVM all variables have 256 bits. ### EVM gas calculations and DOS protection We do not charge users gas for transactions. We do have a protection against Denial of Service attacks. Each transaction needs to submit proof of work (PoW) proportional to the amount of gas that the transaction would have used if we would charge for transactions. We are currently using the same PoW algorithm as Ethereum. $POW = k \times gas$ This PoW is calculated in the browser or other client that submits a transaction and is passed together with the transaction. If the transaction does not include the required PoW it will be rejected. We are still researching the formula for **k**. Ideally **k** should go down if the chain is underloaded and increase if the chain starts to be overloaded. ## Ethereum clients ### Compatibility The goal is to provide compatible JSON client API for client software such as Web3js, Web3py, Metamask and Truffle. # DDoS Protection Source: https://docs.skale.space/concepts/skale-chain/ddos-protection Explore the configurable chain DDoS Protection System Besides limiting the gas consumption rate on SKALE Chains, each chain also comes with a configurable DDOS protection system that allows the Chain to detect peak (per second) and long load (per minute) JSON-RPC calls and WS/WSS connections. The protection enables the chain to survive in high load situations by banning caller origins for a preset number of seconds. An example configuration is: ```json theme={null} "unddos": { "origins": [ { "origin": [ "192.168.1.1", "127.0.0.*", "::1" ], "ban_lengthy": 0, "ban_peak": 0, "max_calls_per_minute": 1000000000, "max_calls_per_second": 1000000000, "max_ws_conn": 65535 }, { "origin": [ "*" ], "ban_lengthy": 120, "ban_peak": 15, "max_calls_per_minute": 5000, "max_calls_per_second": 1500, "max_ws_conn": 20 } ] } ``` The first "origins" block configures allowed unlimited load from specified IP origins. The second origins block configures all call origins allowed, but allow 1500 JSON-RPC calls per second and 5000 calls per minute. If the calls exceed the per second limit, "ban\_peak" bans the caller for 15 seconds. If the calls exceed the per minute limit, "ban\_lengthy" bans the caller for 120 seconds. And finally, "max\_ws\_conn" allows for 20 concurrent connections from a single IP. The configuration settings can be expanded to limit specific JSON-RPC calls, like eth\_blockNumber. For example: ```json theme={null} { "origins": [ { "ban_lengthy": 120, "ban_peak": 15, "custom_method_settings": { "eth_blockNumber": { "max_calls_per_minute": 150000, "max_calls_per_second": 5000 } }, "max_calls_per_minute": 15000, "max_calls_per_second": 500, "max_ws_conn": 50, "origin": ["*"] } ] } ``` And DDoS protection can be completely disabled with the following config: ```json theme={null} "unddos": { "enabled": false, } ``` ## Why It Matters Chain operators can tune these limits to keep RPC endpoints responsive during traffic spikes—protecting node health while preserving the zero-gas, instant-finality experience for legitimate users. # Introduction Source: https://docs.skale.space/concepts/skale-chain/introduction What makes a SKALE Chain a gas-free, instant-finality L1 ## Overview SKALE hosts many elastic SKALE Chains—each a full EVM Layer 1 with zero gas fees and instant finality. Chains can be app-specific, ecosystem hubs, or enterprise/private environments, all secured by the shared validator pool that stakes on Ethereum. Every SKALE Chain comes with: * Full EVM compatibility * Instant finality via BLS threshold signatures * Zero gas fees using sFUEL * Native randomness, file storage, and bridging SKALE Chains support permissionless and permissioned setups. Operators configure validators, access control, and optional gas policies to fit their use case—then rotate privileges, delegate via multisig, or fully renounce for community control. ## What's a SKALE Chain? Every SKALE Chain is its own L1. SKALE is not a rollup or sidechain; chains keep independent state and consensus while Ethereum (and other bases via SKALE Expand) hosts the control-plane contracts. Validator committees rotate across chains, sign blocks with BLS, and deliver instant finality without sacrificing decentralization. # Precompiled Contracts Source: https://docs.skale.space/concepts/skale-chain/precompiled-contracts Precompiled Contracts on SKALE SKALE Chains ship extra precompiled contracts beyond Ethereum’s defaults (e.g., `ecrecover`, `sha256`, `modexp`). These precompiles unlock native file storage, logging, randomness, and configuration helpers without deploying custom contracts—ideal for builders who want performance and simplicity. | Address | Function | Sender Who May Call | Description | | ------- | ------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 0x0A | readChunk | Any | Reads chunk from file from specific position with specific length | | 0x0B | createFile | Filestorage Only | Creates an address for an empty file | | 0x0C | uploadChunk | Filestorage Only | Uploads 1MB chunk of data from specific position in specific file by file owner | | 0x0D | getFileSize | Any | Returns size of file | | 0x0E | deleteFile | Filestorage Only | Deletes file from filestorage system | | 0x0F | createDirectory | Filestorage Only | Creates directory in filestorage system | | 0x10 | deleteDirectory | Filestorage Only | Deletes directory in filestorage system | | 0x11 | calculateFileHash | Any | Calculates and writes SHA256 hash of file in same directory *\.\_hash* | | 0x12 | logTextMessage | Any |
Logs a message:

Used for IMA SKALE Chain Testing

| | 0x13 | getConfigVariableUint256 | Any | Returns SKALE Chain config uint256 for IMA SKALE Chain contracts. Used for reading BLS common public key | | 0x14 | getConfigVariableAddress | Any | Returns SKALE Chain config address for IMA SKALE Chain testing | | 0x15 | getConfigVariableString | Any | Returns SKALE Chain config string for IMA SKALE Chain testing | | 0x16 | fnReserved0x16 | Any | Reserved | | 0x16 | getConfigPermissionsFlag | Any | Returns SKALE Chain config boolean for IMA SKALE Chain testing | | 0x18 | getBlockRandom | Any | Return a random number based on BLS threshold signature common coin of the current block | | 0x19 | getIMABLSPublicKey | Any | Returns relevant BLSPublicKey according to block timestamp | Use these precompiles to build richer experiences (randomness for gaming, native storage for media, config reads for bridges) without extra gas costs or custom infra. # Snapshots Source: https://docs.skale.space/concepts/skale-chain/snapshots SKALE Chain Snapshots ## Introduction One of the main features of SKALE Network is [node rotation](../skale-network/pooled-security-model#node-rotation), and this is performed through a **BLS Snapshot** process. Briefly, if node **A** leaves a SKALE Chain for any reason (e.g. random rotation, node exit, etc.), another node **B** will be chosen to replace it. Once node **B** is chosen, it needs all the information about previous blocks, transactions, etc. from the SKALE chain. SKALE Network solves this by periodically performing snapshots of the SKALE chain file system on each node so that nodes can share the latest snapshot with incoming node **B**. Additionally, a node can be restarted from a snapshot if the node was offline for a long period of time, if it cannot catch up with other nodes using SKALE Consensus' catch-up algorithm. ## Design **skaled** uses the **btrfs** to create snapshots. **btrfs (B-tree File System)** Linux filesystem designed for high performance and reliability, featuring built-in support for snapshots, checksums, compression, and pooling of multiple storage devices. Instead of overwriting data in place, Btrfs writes new changes to a fresh location and updates pointers to them. This makes it possible to take instant, space-efficient snapshots, detect data corruption using checksums, and roll back safely without slowing the system down. **Assumptions:** 1. First block on SKALE chain occurred at $T_{firstBlock}$ 2. Node does snapshot every $T_{snapshotInterval}$ seconds (configurable number, stored in SKALE chain config, so it is similar for all nodes in SKALE chain) Assume **k** snapshots were already done. Let's see when **(k+1)**-th will be done and ready to be used: 1. **(k+1)**-th snapshot will be done once another block's **B** timestamp crosses boundary $[T_{firstBlock} + T_{snapshotInterval}] + 1 \times T_{snapshotInterval}$ 2. Node updates **last\_snapshotted\_block\_with\_hash** with **k**-th snapshot block number 3. If it is time to do snapshot and node already has 3 snapshots stored, it deletes the latest of them 4. Node creates a snapshot **S\_latest** 5. Node updates **last\_snapshot\_creation\_time** with **B**'s timestamp 6. Node calculates **S\_latest**'s hash in separate thread (computes hash of every file in this snapshot including filestorage), assuming it will be successfully calculated before next snapshot is done. So **(k+1)**-th snapshot will be ready to be used only when **(k+2)**-th snapshot will be performing 7. Node updates **stateRoot** field with **k**-th snapshot hash To start from a snapshot, a node must confirm whether a snapshot is valid. To prevent downloading of snapshots from malicious nodes, the following procedure was designed: 1. Node **A** chooses a random node from SKALE chain and requests the last snapshot block number 2. Node **A** requests all nodes from the SKALE chain to send a snapshot hash signed with its corresponding BLS key 3. Once node **A** receives all hashes and signatures, **A** tries to choose a hash **H** similar on at least 2/3 + 1 nodes, then **A** collects their BLS signatures into one and verifies it. (If steps 1–3 fail, a node will not start.) 4. Node **A** chooses a random node from those 2/3 + 1 nodes and downloads a snapshot from it, computes its hash and confirms whether it is similar to **H**. If it is similar then a node starts from this snapshot, otherwise node **A** will attempt to download a snapshot from another node **stateRoot** is needed to determine whether there are any node software issues. Each time a new block is passed from consensus, a node compares its own stateRoot with the stateRoot of the incoming block (so snapshot hashes of the last block from different nodes are compared). If the stateRoots fail to match, then a software issue is assumed and the node must restart from a snapshot. ## JSON-RPC Snapshot Methods ### skale\_getSnapshot Parameters * **blockNumber**: integer, a block number * **autoCreate**: Boolean, create snapshot if it does not exist Returns * **dataSize**: integer, the size of snapshot in bytes * **maxAllowedChunkSize**: integer, the maximum chunk size in bytes Example ```sh theme={null} // Request curl -X POST --data '{ "jsonrpc": "2.0", "method": "skale_getSnapshot", "params": { "blockNumber": 68, "autoCreate": false }, "id": 73 }' // Result { "id": 73, "dataSize": 12345, "maxAllowedChunkSize": 1234 } ``` ### skale\_downloadSnapshotFragment Returns a snapshot fragment. Parameters * **blockNumber**: a block number, or the string "latest" * **from**: a block number * **size**: integer, the size of fragment in bytes * **isBinary**: Boolean Returns * **size**: integer, the size of chunk in bytes * **data**: base64, btrfs data Example ```sh theme={null} // Request curl -X POST --data '{ "jsonrpc": "2.0", "method": "skale_downloadSnapshotFragment", "params": { "blockNumber": "latest", "from": 0, "size": 1024, "isBinary": false }, "id": 73 }' // Result { "id": 73, "size": 1234, "data": "base64 here" } ``` ### skale\_getSnapshotSignature Returns signature of snapshot hash on given block number. Parameters * **blockNumber**: integer, a block number Returns * **X**: string, X coordinate of signature * **Y**: string, Y coordinate of signature * **helper**: integer, minimum number such that Y=(X+helper)^3 is a square in Fq * **hash**: string, hash of a snapshot on given block number * **signerIndex**: integer, receiver's index in SKALE chain Example ```sh theme={null} // Request curl -X POST --data '{ "jsonrpc": "2.0", "method": "skale_getSnapshotSignature", "params": [ 14 ], "id": 73 }' // Result { "id": 73, "X": 3213213131313566131315664653132135156165496800065461326, "Y": 3164968456435613216549864300564646631198986113213166, "helper": 1, "hash": aef45664dcb5636, "signerIndex": 1 } ``` ### skale\_getLatestSnapshotBlockNumber Returns the latest snapshotted block's number. Parameters NULL Returns * **blockNumber**: integer, the latest snapshotted block's number Example ```sh theme={null} // Request curl -X POST --data '{ "jsonrpc": "2.0", "method": "skale_getLatestSnapshotBlockNumber", "params": { }, "id": 73 }' // Result { "id": 73, "blockNumber": 15 } ``` # SKALE Expand Source: https://docs.skale.space/concepts/skale/skale-expand Bringing Private Execution, Instant Finality, and Infinite Scalability to any Blockchain For over five (5) years, SKALE has often been confused as both a Layer 1 and a Layer 2. SKALE Expand leans into the truth of what SKALE is -- an application capable of launching many blockchains into the form of a network and embodies the technical growth that SKALE is capable of; bringing private and scalable blockchain compute to every ecosystem, ushering in the growth and adoption of artificial intelligence agentic commerce. [SKALE on Base](/get-started/quick-start/skale-on-base) is the first SKALE Expand network. ## SKALE Expand ### About the Network Deployments * The base component of SKALE Network on Ethereum is SKALE Manager * SKALE Expand creates new deployments of SKALE Manager on a new EVM Chain (can be expanded to non-EVM) * New deployments have 100% separate pools of compute * The truly canonical deployment of SKL remains on Ethereum Mainnet. All subsequent deployments of SKL are bridged and are not able to create new SKL tokens without being locked on Ethereum. * New deployments can have bespoke features that are not available on other deployments * sChains created on a specific deployment do not impact the resources of another deployment ### About the SKALE Chains * Each SKALE Chain (sChain) is a Layer 1 blockchain * sChains are *NOT* rollups and do not push state or checkpoints back to the parent chain (regardless of Layer) * sChains can be application-specific blockchains for a single entity, shared chains for a group of entities, or fully permissionless blockchains allowing anyone to consume the resources * sChains can now be created off of any blockchain network that has a SKALE Manager deployment Want to learn more? Checkout the [official blog post here](https://blog.skale.space/blog/skale-expand-bringing-gas-free-instant-private-execution-to-any-evm-blockchain). # Introduction to Threshold Schemes Source: https://docs.skale.space/concepts/threshold-schemes Introduction to Threshold Schemes Threshold schemes let a group share control of a secret (like a private key) so no single participant can use it alone. On SKALE, threshold cryptography powers BLS signatures, DKG, and pooled security. Here’s the high-level story. ## The basic idea A `(k, n)` threshold scheme splits a secret into `n` shares. Any `k` shares can reconstruct the secret; fewer than `k` reveal nothing. This is typically done with polynomials: pick a polynomial where the secret is the constant term, hand each participant a point on that curve, and require `k` points to rebuild it. ## Shamir’s Secret Sharing (SSS) SSS is the classic approach: a dealer creates the polynomial, hands out shares, and `k` participants can later interpolate to recover the secret. The downside: the dealer knows everything, so you must trust them. ## From SSS to Distributed Key Generation (DKG) DKG removes the trusted dealer. Every participant creates their own polynomial and shares a piece of it with everyone else. When all the partial polynomials are summed, the group gets: * A public key (from the summed public polynomials) * Private key shares (each participant holds their own summed share) No single party ever sees the full secret key, yet the group can still produce threshold signatures. ## Why SKALE uses threshold crypto * **Security against collusion** – You need a supermajority of validators to act together to sign or decrypt. * **Instant finality with BLS** – Validators combine shares into a single BLS signature that proves supermajority agreement. * **Rotation-friendly** – New committees can rerun DKG to refresh keys every epoch. For a deeper dive into how SKALE runs DKG and BLS in production, see the DKG with BLS page. # Build an Agent Source: https://docs.skale.space/cookbook/agents/build-an-agent Build AI agents that interact with onchain data, make payments, and access paywalled resources ## Build an Agent Build agents that can autonomously interact with APIs, make x402 payments, and access paywalled resources. This guide covers two approaches: a basic agent for simple use cases and an AI-powered agent using LangChain for more complex decision-making. ## Prerequisites * Node.js and npm installed * A SKALE Chain endpoint * Understanding of x402 protocol (see [Make Payments](/cookbook/x402/buying)) * Familiarity with TypeScript/JavaScript * A wallet with funds (USDC or supported token) * Anthropic API key (for LangChain agent with Claude) ## Overview | Agent Type | Best For | | ------------------- | ------------------------------------------------------------------------ | | **Basic Agent** | Simple automation, scripts, scheduled tasks | | **LangChain Agent** | AI-powered services with payment protection, server-side AI integrations | *** ## Implementation A basic agent handles x402 payments programmatically without AI. Perfect for automated scripts, bots, or services that need to access paywalled resources. ### Step 1: Install Dependencies ```bash theme={null} npm install @x402/core @x402/evm viem dotenv ``` ### Step 2: Set Up Environment Variables Create a `.env` file: ```bash theme={null} # Your wallet private key (never commit this!) PRIVATE_KEY=0xYourPrivateKey # USDC token contract on SKALE PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)" ``` ### Step 3: Define Your Chain Create a file `chain.ts`: ```typescript theme={null} import { defineChain } from "viem"; export const skaleChain = defineChain({ id: 324705682, name: "SKALE Base Sepolia", nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" }, rpcUrls: { default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] }, }, }); ``` ### Step 4: Create the Agent Class Create a file `agent.ts`: ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; import { createPublicClient, http, formatEther } from "viem"; import { skaleChain } from "./chain"; import "dotenv/config"; type AccessResult = { success: boolean; data?: unknown; error?: string; }; class BasicAgent { private httpClient: x402HTTPClient; private walletAddress: string; private publicClient: ReturnType; private constructor( httpClient: x402HTTPClient, walletAddress: string, publicClient: ReturnType ) { this.httpClient = httpClient; this.walletAddress = walletAddress; this.publicClient = publicClient; } static async create(): Promise { const privateKey = process.env.PRIVATE_KEY; if (!privateKey) { throw new Error("PRIVATE_KEY environment variable is required"); } // Create wallet account from private key const account = privateKeyToAccount(privateKey as `0x${string}`); // Create EVM scheme for signing payments const evmScheme = new ExactEvmScheme(account); // Register scheme for all EVM networks const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); // Create public client for balance checks const publicClient = createPublicClient({ chain: skaleChain, transport: http(), }); console.log(`Agent initialized with wallet: ${account.address}`); return new BasicAgent(httpClient, account.address, publicClient); } async accessResource(url: string): Promise { console.log(`Accessing resource: ${url}`); try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); if (response.status === 402) { return this.handlePaymentRequired(response, url); } if (!response.ok) { return { success: false, error: `Request failed: ${response.status}` }; } const data = await response.json(); return { success: true, data }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return { success: false, error: message }; } } private async handlePaymentRequired( response: Response, url: string ): Promise { console.log("Payment required (402), processing payment..."); try { const responseBody = await response.json(); // Get payment requirements from response headers and body const paymentRequired = this.httpClient.getPaymentRequiredResponse( (name: string) => response.headers.get(name), responseBody ); console.log(`Payment options: ${paymentRequired.accepts.length}`); // Create signed payment payload const paymentPayload = await this.httpClient.createPaymentPayload(paymentRequired); // Encode payment headers for the retry request const paymentHeaders = this.httpClient.encodePaymentSignatureHeader(paymentPayload); // Retry request with payment const paidResponse = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", ...paymentHeaders, }, }); if (!paidResponse.ok) { return { success: false, error: `Payment failed: ${paidResponse.status}` }; } // Check settlement response const settlement = this.httpClient.getPaymentSettleResponse( (name: string) => paidResponse.headers.get(name) ); if (settlement?.transaction) { console.log(`Payment settled, tx: ${settlement.transaction}`); } const data = await paidResponse.json(); console.log("Resource accessed successfully after payment!"); return { success: true, data }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; console.error("Payment processing failed:", message); return { success: false, error: message }; } } } export default BasicAgent; ``` ### Step 5: Use the Agent Create a file `index.ts`: ```typescript theme={null} import BasicAgent from "./agent"; async function main() { // Create the agent (async factory pattern) const agent = await BasicAgent.create(); // Access a paywalled resource const result = await agent.accessResource("http://localhost:3000/premium/data"); if (result.success) { console.log("Received data:", result.data); } else { console.error("Error:", result.error); } } main().catch(console.error); ``` ### Step 6: Run the Agent ```bash theme={null} npx tsx index.ts ``` Build an AI-powered weather forecast service that uses LangChain with Claude to generate forecasts, protected by x402 payments. This example demonstrates a complete setup with three components: an AI agent, a payment-protected server, and a client that handles x402 payments automatically. ### Architecture Overview | Component | Description | | ------------------------ | -------------------------------------------------------------------------- | | **Agent** (`agent.ts`) | Uses LangChain + Claude to generate AI-powered weather forecasts | | **Server** (`server.ts`) | Hono server with x402 middleware that protects the weather endpoint | | **Client** (`client.ts`) | Handles x402 payment flow automatically when accessing protected resources | ### Step 1: Install Dependencies ```bash theme={null} npm install @x402/core @x402/evm @x402/hono @langchain/anthropic @langchain/core hono @hono/node-server viem dotenv ``` ### Step 2: Set Up Environment Variables Create a `.env` file: ```bash theme={null} # Anthropic API key for Claude. It can also be used other LLMs ANTHROPIC_API_KEY=your_anthropic_api_key # Your wallet private key (never commit this!) PRIVATE_KEY=0xYourPrivateKey # Address to receive payments RECEIVING_ADDRESS=0x71dc0Bc68e7f0e2c5aaCE661b0F3Fb995a80AAF4 # Facilitator URL for payment processing FACILITATOR_URL=https://facilitator.dirtroad.dev # Network configuration NETWORK_CHAIN_ID=324705682 # Payment token (Axios USD on SKALE Base Sepolia) PAYMENT_TOKEN_ADDRESS=0x61a26022927096f444994dA1e53F0FD9487EAfcf PAYMENT_TOKEN_NAME=Axios USD # Server port PORT=3001 ``` Never commit your private key or API keys to version control. Add `.env` to your `.gitignore` file. ### Step 3: Define Your Chain Create a file `chain.ts`: ```typescript theme={null} export const skaleChain = { id: 324705682, name: "SKALE Base Sepolia Testnet", rpcUrls: { default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] } }, blockExplorers: { default: { name: "Blockscout", url: "https://base-sepolia-testnet-explorer.skalenodes.com/" } }, nativeCurrency: { name: "credits", decimals: 18, symbol: "CREDIT" } }; ``` ### Step 4: Create the AI Agent The agent uses LangChain with Claude to generate realistic weather forecasts. Create a file `agent.ts`: ```typescript theme={null} import { ChatAnthropic } from "@langchain/anthropic"; import { HumanMessage, SystemMessage } from "@langchain/core/messages"; import "dotenv/config"; type WeatherDay = { dayOfWeek: string; date: string; minTemp: number; maxTemp: number; condition: "sunny" | "rainy"; }; type WeatherResponse = { city: string; forecast: WeatherDay[]; }; class WeatherAgent { private model: ChatAnthropic; constructor() { const apiKey = process.env.ANTHROPIC_API_KEY; if (!apiKey) { throw new Error("ANTHROPIC_API_KEY environment variable is required"); } this.model = new ChatAnthropic({ model: "claude-sonnet-4-20250514", temperature: 0.7, anthropicApiKey: apiKey, }); console.log("[WeatherAgent] Initialized"); } async getWeatherForecast(city: string): Promise { console.log(`[WeatherAgent] Getting forecast for: ${city}`); const systemPrompt = `You are a weather forecast assistant. When asked about weather for a city, respond ONLY with a valid JSON object (no markdown, no explanation). The JSON must have this exact structure: { "city": "City Name", "forecast": [ { "dayOfWeek": "Monday", "date": "December 16", "minTemp": 5, "maxTemp": 12, "condition": "sunny" } ] } Rules: - Provide exactly 5 days starting from today - dayOfWeek must be the full day name - date must be in "Month Day" format - minTemp and maxTemp must be integers in Celsius - condition must be either "sunny" or "rainy" - Make temperatures realistic for the city and season`; const userPrompt = `Get the weather forecast for ${city} for the next 5 days.`; const response = await this.model.invoke([ new SystemMessage(systemPrompt), new HumanMessage(userPrompt), ]); const content = response.content as string; const weatherData = JSON.parse(content) as WeatherResponse; console.log(`[WeatherAgent] Forecast generated for ${city}`); return weatherData; } } export default WeatherAgent; ``` ### Step 5: Create the Payment-Protected Server The server uses x402 middleware to protect the weather endpoint, requiring payment before returning forecasts. Create a file `server.ts`: ```typescript theme={null} import { Hono } from "hono"; import { cors } from "hono/cors"; import { serve } from "@hono/node-server"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import type { Network } from "@x402/core/types"; import WeatherAgent from "./agent.js"; import "dotenv/config"; const app = new Hono(); const PORT = Number(process.env.PORT) || 3001; app.use("*", cors()); let agent: WeatherAgent | null = null; async function initializeAgent() { try { agent = new WeatherAgent(); console.log("[Server] Weather agent initialized"); } catch (error) { console.warn("[Server] Agent initialization failed:", error); } } app.get("/api/health", (c) => { return c.json({ status: "ok", agentInitialized: agent !== null, }); }); async function setupWeatherRoute() { const facilitatorUrl = process.env.FACILITATOR_URL; const receivingAddress = process.env.RECEIVING_ADDRESS as `0x${string}`; const networkChainId = process.env.NETWORK_CHAIN_ID || "324705682"; const paymentTokenAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`; const paymentTokenName = process.env.PAYMENT_TOKEN_NAME || "Axios USD"; if (!facilitatorUrl || !receivingAddress || !paymentTokenAddress) { console.warn("[Server] Payment middleware not configured"); app.get("/api/weather", (c) => { return c.json({ error: "Payment middleware not configured" }, 503); }); return; } const network: Network = `eip155:${networkChainId}`; // Initialize facilitator client const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl }); const resourceServer = new x402ResourceServer(facilitatorClient); resourceServer.register("eip155:*", new ExactEvmScheme()); // Configure payment middleware for the weather endpoint app.use( paymentMiddleware( { "GET /api/weather": { accepts: [ { scheme: "exact", network: network, payTo: receivingAddress, price: { amount: "10000", // 0.01 tokens (6 decimals) asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1", }, }, }, ], description: "AI Weather Forecast - 5 day forecast", mimeType: "application/json", }, }, resourceServer ) ); // Protected weather endpoint app.get("/api/weather", async (c) => { if (!agent) { return c.json({ error: "Agent not initialized" }, 503); } const city = "London"; // Hardcoded for demo console.log(`[Server] Generating forecast for: ${city}`); try { const forecast = await agent.getWeatherForecast(city); return c.json({ success: true, timestamp: new Date().toISOString(), data: forecast, }); } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; return c.json({ error: message }, 500); } }); console.log("[Server] Weather route configured with x402 payment protection"); } async function startServer() { await initializeAgent(); await setupWeatherRoute(); serve({ fetch: app.fetch, port: PORT }, () => { console.log(`[Server] Running on http://localhost:${PORT}`); console.log(`[Server] Weather endpoint: GET /api/weather (payment required)`); }); } startServer(); ``` ### Step 6: Create the Client The client automatically handles the x402 payment flow when it receives a 402 response. Create a file `client.ts`: ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { createPublicClient, http, formatEther } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { skaleChain } from "./chain.js"; import "dotenv/config"; type WeatherResponse = { success: boolean; timestamp: string; data: { city: string; forecast: Array<{ dayOfWeek: string; date: string; minTemp: number; maxTemp: number; condition: "sunny" | "rainy"; }>; }; }; type AccessResult = { success: boolean; data?: T; error?: string; }; export class WeatherClient { private httpClient: x402HTTPClient; private walletAddress: string; private publicClient: any; private baseUrl: string; private constructor( httpClient: x402HTTPClient, walletAddress: string, publicClient: any, baseUrl: string ) { this.httpClient = httpClient; this.walletAddress = walletAddress; this.publicClient = publicClient; this.baseUrl = baseUrl; } static async create(baseUrl: string = "http://localhost:3001"): Promise { const privateKey = process.env.PRIVATE_KEY; if (!privateKey) { throw new Error("PRIVATE_KEY environment variable is required"); } // Create wallet from private key const account = privateKeyToAccount(privateKey as `0x${string}`); // Setup x402 client with EVM scheme const evmScheme = new ExactEvmScheme(account); const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); const publicClient = createPublicClient({ chain: skaleChain, transport: http(), }); console.log(`[Client] Initialized with wallet: ${account.address}`); return new WeatherClient(httpClient, account.address, publicClient, baseUrl); } async getWeather(): Promise> { const url = `${this.baseUrl}/api/weather`; console.log("[Client] Requesting weather forecast..."); try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); // Handle 402 Payment Required if (response.status === 402) { return this.handlePaymentRequired(response, url); } if (!response.ok) { const errorBody = await response.text(); return { success: false, error: `Request failed: ${response.status} - ${errorBody}` }; } const data = (await response.json()) as WeatherResponse; console.log("[Client] Weather data received!"); return { success: true, data }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; console.error("[Client] Error:", message); return { success: false, error: message }; } } private async handlePaymentRequired( response: Response, url: string ): Promise> { console.log("[Client] Payment required (402), processing..."); try { const responseBody = await response.json(); // Parse payment requirements from response const paymentRequired = this.httpClient.getPaymentRequiredResponse( (name: string) => response.headers.get(name), responseBody ); console.log("[Client] Payment options:", paymentRequired.accepts.length); // Create signed payment payload const paymentPayload = await this.httpClient.createPaymentPayload(paymentRequired); console.log("[Client] Payment payload created"); // Encode payment headers const paymentHeaders = this.httpClient.encodePaymentSignatureHeader(paymentPayload); // Retry request with payment console.log("[Client] Retrying with payment..."); const paidResponse = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", ...paymentHeaders, }, }); if (!paidResponse.ok) { const errorBody = await paidResponse.text(); return { success: false, error: `Payment failed: ${paidResponse.status} - ${errorBody}` }; } // Check settlement response const settlement = this.httpClient.getPaymentSettleResponse( (name: string) => paidResponse.headers.get(name) ); if (settlement?.transaction) { console.log("[Client] Payment settled, tx:", settlement.transaction); } const data = (await paidResponse.json()) as WeatherResponse; console.log("[Client] Weather data received after payment!"); return { success: true, data }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; console.error("[Client] Payment failed:", message); return { success: false, error: message }; } } } export default WeatherClient; ``` ### Step 7: Create a Index.ts to Test Create a file `index.ts` to test the full flow: ```typescript theme={null} import { WeatherClient } from "./client.js"; async function main() { console.log("Initializing Weather Client...\n"); const client = await WeatherClient.create(); console.log("Requesting weather forecast...\n"); const result = await client.getWeather(); if (result.success && result.data) { console.log(`Weather Forecast for ${result.data.data.city}:`); console.log("-".repeat(50)); for (const day of result.data.data.forecast) { console.log(` ${day.dayOfWeek} (${day.date}): ${day.condition} - ${day.minTemp}C to ${day.maxTemp}C`); } console.log("-".repeat(50)); console.log(`Generated: ${result.data.timestamp}`); } else { console.error("Failed to get weather:", result.error); } } main().catch(console.error); ``` ### Step 8: Run the Example 1. Start the server in one terminal: ```bash theme={null} npx tsx server.ts ``` 2. Run the client in another terminal: ```bash theme={null} npx tsx index.ts ``` Example output: ``` Initializing Weather Client... Requesting weather forecast... [Client] Requesting weather forecast... [Client] Payment required (402), processing... [Client] Payment options: 1 [Client] Payment payload created [Client] Retrying with payment... [Client] Payment settled, tx: 0xabc123... [Client] Weather data received after payment! Weather Forecast for London: -------------------------------------------------- Monday (December 16): rainy - 5C to 9C Tuesday (December 17): rainy - 4C to 8C Wednesday (December 18): sunny - 3C to 7C Thursday (December 19): sunny - 2C to 6C Friday (December 20): rainy - 4C to 8C -------------------------------------------------- Generated: 2024-12-16T10:30:00.000Z ``` *** ## Best Practices 1. **Set Spending Limits**: Implement maximum payment amounts to prevent unexpected costs 2. **Use Environment Variables**: Never hardcode private keys or API keys 3. **Log Transactions**: Keep records of all payments for debugging and accounting 4. **Handle Errors Gracefully**: Always wrap payment logic in try-catch blocks 5. **Test on Testnet First**: Use SKALE testnet before deploying to production ## Security Considerations * Never expose private keys in client-side code or logs * Use secure key management (environment variables, secrets manager) * Implement rate limiting to prevent abuse * Consider using a dedicated wallet with limited funds for agents * Audit your agent's behavior regularly ## Next Steps * [Accept Payments (Seller)](/cookbook/x402/accepting-payments) * [Run a Facilitator](/cookbook/x402/facilitator) * [Multi-Token Payments](/cookbook/x402/non-usdc-tokens) ## Resources * [AI Agents Use Case](/get-started/use-cases/ai-agents) * [LangChain Documentation](https://js.langchain.com/docs) * [x402 Examples Repository](https://github.com/TheGreatAxios/x402-examples) # Setup Foundry Source: https://docs.skale.space/cookbook/deployment/setup-foundry Guide to setting up Foundry for SKALE development ## Setup Foundry Foundry is a blazing fast, portable, and modular toolkit for Ethereum application development written in Rust. This guide will help you set up Foundry for developing on SKALE. ## Prerequisites * [Rust](https://www.rust-lang.org/tools/install) installed on your machine * Basic command-line knowledge If you are on a Windows machine, you will need to use Windows Subsystem for Linux (WSL), since Foundry doesn't work natively on Windows. ## Step 1: Install Foundry Install Foundry by running: ```shell theme={null} foundryup ``` This will install the latest stable release of Foundry, including: * **Forge**: Build, test, and deploy smart contracts * **Cast**: Interact with EVM smart contracts, send transactions, and read chain data * **Anvil**: Local Ethereum node for testing * **Chisel**: Solidity REPL ## Step 2: Verify Installation Verify that Foundry is installed correctly: ```shell theme={null} forge --version cast --version anvil --version ``` You should see version numbers for each tool. ## Step 3: Create a New Foundry Project Create a new Foundry project: ```shell theme={null} forge init my-skale-project cd my-skale-project ``` This creates a new directory with the following structure: ``` my-skale-project/ ├── src/ # Smart contracts ├── script/ # Deployment scripts ├── test/ # Test files ├── lib/ # Dependencies ├── foundry.toml # Configuration file └── remappings.txt # Import remappings ``` ## Step 4: Configure Foundry for SKALE Update the `foundry.toml` file to add SKALE Chain endpoints: ```toml theme={null} [profile.default] src = "src" out = "out" libs = ["lib"] # SKALE Chain Configuration [rpc_endpoints] skale_testnet = "https://testnet.skalenodes.com/v1/juicy-low-small-testnet" skale_mainnet = "https://mainnet.skalenodes.com/v1/elated-tan-skat" # Add your custom SKALE Chain endpoint here [etherscan] skale_testnet = { key = "", url = "https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api" } ``` Replace the RPC URLs with your specific SKALE Chain endpoint. You can find SKALE Chain endpoints in the [SKALE Portal](https://portal.skale.space). ## Step 5: Install Dependencies Install OpenZeppelin Contracts (or other dependencies): ```shell theme={null} forge install OpenZeppelin/openzeppelin-contracts ``` This will add the dependency to your `lib` directory. ## Step 6: Create a Keystore (Optional but Recommended) For secure deployment, create a Foundry keystore: ```shell theme={null} cast wallet import skale-deployer --private-key $(cast wallet new | grep 'Private key:' | awk '{print $3}') ``` Provide a password to encrypt the keystore file. If you forget this password, you will not be able to recover it. Get your wallet address: ```shell theme={null} cast wallet address --account skale-deployer ``` Copy this address and fund it with sFUEL from the [sFUEL Station](https://sfuelstation). ## Step 7: Test Your Setup Compile your contracts: ```shell theme={null} forge build ``` Run tests: ```shell theme={null} forge test ``` ## SKALE-Specific Considerations When deploying to SKALE, remember to: 1. **Use the `--legacy` flag**: SKALE Chains require legacy transaction format ```shell theme={null} forge create src/MyContract.sol:MyContract \ --account skale-deployer \ --rpc-url skale_testnet \ --broadcast \ --legacy ``` 2. **Use `--slow` flag for scripts**: When running scripts with multiple transactions ```shell theme={null} forge script script/Deploy.s.sol \ --account skale-deployer \ --rpc-url skale_testnet \ --broadcast \ --slow \ --legacy ``` 3. **Verify contracts**: Use the block explorer API for verification ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyContract.sol:MyContract \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` ## Next Steps Now that Foundry is set up, you can: * [Deploy an ERC-20 Token](/cookbook/smart-contracts/deploy-erc20-token) * [Deploy an ERC-721 Token](/cookbook/smart-contracts/deploy-erc721-token) * [Deploy an ERC-1155 Token](/cookbook/smart-contracts/deploy-erc1155-token) * [Solidity Quickstart](/developers/solidity-quickstart) ## Related Topics * [Setup Hardhat](/cookbook/deployment/setup-hardhat) * [Deploy on SKALE](/developers/deploy-on-skale) * [Troubleshooting](/developers/troubleshooting) # Setup Hardhat Source: https://docs.skale.space/cookbook/deployment/setup-hardhat Guide to setting up Hardhat for SKALE development ## Setup Hardhat Hardhat is a development environment for Ethereum software. It helps developers compile, deploy, test, and debug their Ethereum applications. This guide will help you set up Hardhat for developing on SKALE. ## Prerequisites * Node.js (v16 or higher) installed * npm or yarn package manager * Basic knowledge of JavaScript/TypeScript ## Step 1: Create a New Project Create a new directory and initialize a Node.js project: ```shell theme={null} mkdir my-skale-project cd my-skale-project npm init -y ``` ## Step 2: Install Hardhat Install Hardhat and its dependencies: ```shell theme={null} npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox ``` The `@nomicfoundation/hardhat-toolbox` package includes all commonly used plugins and tools for Hardhat development. ## Step 3: Initialize Hardhat Initialize a Hardhat project: ```shell theme={null} npx hardhat init ``` Select "Create a JavaScript project" (or TypeScript if preferred) and follow the prompts. ## Step 4: Configure Hardhat for SKALE Update your `hardhat.config.js` (or `hardhat.config.ts`) file: ```javascript theme={null} require("@nomicfoundation/hardhat-toolbox"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: { version: "0.8.19", settings: { optimizer: { enabled: true, runs: 200, }, }, }, networks: { skale_testnet: { url: "https://testnet.skalenodes.com/v1/juicy-low-small-testnet", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], chainId: 1444673419, }, skale_mainnet: { url: "https://mainnet.skalenodes.com/v1/elated-tan-skat", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], chainId: 2046399126, }, }, etherscan: { apiKey: { skale_testnet: "your-api-key", // Not required for Blockscout }, customChains: [ { network: "skale_testnet", chainId: 1444673419, urls: { apiURL: "https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api", browserURL: "https://juicy-low-small-testnet.explorer.testnet.skalenodes.com", }, }, ], }, }; ``` Replace the RPC URLs with your specific SKALE Chain endpoint. You can find SKALE Chain endpoints in the [SKALE Portal](https://portal.skale.space). ## Step 5: Install OpenZeppelin Contracts Install OpenZeppelin Contracts: ```shell theme={null} npm install @openzeppelin/contracts ``` ## Step 6: Create Environment File Create a `.env` file in your project root: ```env theme={null} PRIVATE_KEY=your-private-key-here ``` Never commit your `.env` file to version control. Add it to your `.gitignore` file. Install dotenv package: ```shell theme={null} npm install dotenv ``` Update your `hardhat.config.js` to load environment variables: ```javascript theme={null} require("@nomicfoundation/hardhat-toolbox"); require("dotenv").config(); // ... rest of config ``` ## Step 7: Create a Deployment Script Create a deployment script in `scripts/deploy.js`. Run: ```shell theme={null} touch scripts/deploy.js ``` Add the deployment code: ```javascript theme={null} const hre = require("hardhat"); async function main() { const MyContract = await hre.ethers.getContractFactory("MyContract"); const myContract = await MyContract.deploy(); await myContract.waitForDeployment(); console.log("Contract deployed to:", await myContract.getAddress()); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); ``` ## Step 8: Compile Contracts Compile your contracts: ```shell theme={null} npx hardhat compile ``` ## Step 9: Run Tests Run your tests: ```shell theme={null} npx hardhat test ``` ## SKALE-Specific Considerations When deploying to SKALE with Hardhat: 1. **Transaction Type**: Hardhat automatically handles transaction types, but ensure your Hardhat version supports legacy transactions if needed 2. **Gas Configuration**: SKALE has zero gas fees, but you still need sFUEL for transactions: ```javascript theme={null} networks: { skale_testnet: { url: "...", accounts: [...], gasPrice: 0, // SKALE has zero gas fees }, }, ``` 3. **Contract Verification**: Verify contracts using Hardhat's verify plugin: ```shell theme={null} npx hardhat verify --network skale_testnet ``` ## Project Structure Your Hardhat project should look like this: ``` my-skale-project/ ├── contracts/ # Smart contracts ├── scripts/ # Deployment scripts ├── test/ # Test files ├── hardhat.config.js ├── .env └── package.json ``` ## Next Steps Now that Hardhat is set up, you can: * [Deploy an ERC-20 Token](/cookbook/smart-contracts/deploy-erc20-token) * [Deploy an ERC-721 Token](/cookbook/smart-contracts/deploy-erc721-token) * [Deploy an ERC-1155 Token](/cookbook/smart-contracts/deploy-erc1155-token) * [Deploy on SKALE](/developers/deploy-on-skale) ## Related Topics * [Setup Foundry](/cookbook/deployment/setup-foundry) * [Solidity Quickstart](/developers/solidity-quickstart) * [Troubleshooting](/developers/troubleshooting) # Corbits Source: https://docs.skale.space/cookbook/facilitators/using-corbits Integrate Corbits facilitator for x402 payment processing Corbits is a hosted facilitator service that streamlines x402 payment processing. By using Corbits’ endpoints, you can offload payment verification and settlement without needing to operate your own facilitator infrastructure. ## Prerequisites * Node.js and npm installed * A SKALE Chain endpoint * Basic understanding of x402 protocol ## Configuration ### Environment Variables Create a `.env` file with Corbits configuration: ```bash theme={null} # Receiver address for the x402 payment RECEIVING_ADDRESS=0xAddress_Receiving_Payment # please don't share your private key with anyone PRIVATE_KEY=0xyour_pk ``` ### SDK import Please install the following Viem and Farmeter SDKs created by the Corbits team: ```bash theme={null} npm install viem ``` ```bash theme={null} npm install @faremeter/info ``` ```bash theme={null} npm install @faremeter/middleware ``` ## Integration with Corbits ```typescript theme={null} import { serve } from "@hono/node-server"; import { Hono } from "hono"; import { hono as middleware } from "@faremeter/middleware"; import { evm } from "@faremeter/info"; import "dotenv/config"; const app = new Hono(); const receiver_address = process.env.RECEIVING_ADDRESS || "0xsome_default_address"; const facilitator = "https://facilitator.corbits.dev"; const paywalledMiddleware = await middleware.createMiddleware({ facilitatorURL: facilitator, accepts: [ evm.x402Exact({ network: "eip155:324705682", asset: "USDC", amount: "1000", // $0.0001 in USDC base units payTo: receiver_address, }), ], }); app.get("/api/free", (c) => { return c.json({ type: "free", message: "This is free data that does not require payment", timestamp: new Date().toISOString(), data: { temperature: 72, humidity: 45, conditions: "Sunny", }, }); }); // Premium endpoint with payment required app.get("/api/premium", paywalledMiddleware, (c) => { return c.json({ type: "paid", message: "This is is a paid data that does require x402 payment", timestamp: new Date().toISOString(), data: { temperature: 72, humidity: 45, conditions: "Sunny", }, }); }); serve(app, (info) => { console.log(`Listening on http://localhost:${info.port}`); }); ``` ```typescript theme={null} import { createLocalWallet } from "@faremeter/wallet-evm"; import { createPaymentHandler } from "@faremeter/payment-evm/exact"; import { wrap as wrapFetch } from "@faremeter/fetch"; import {skaleBaseSepoliaTestnet} from "viem/chains"; import "dotenv/config"; const pk = process.env.PRIVATE_KEY if (!pk) { throw new Error("PRIVATE_KEY must be set in your environment"); } const url = `http://localhost:3000/api/premium`; const wallet = await createLocalWallet(skaleBaseSepoliaTestnet, pk); const fetchWithPayer = wrapFetch(fetch, { handlers: [createPaymentHandler(wallet)], }); const response = await fetchWithPayer(url); if (response.ok) { const data = await response.json(); console.log("Response data:", data); } else { console.error(`Error: ${response.statusText}`); } ``` ## Troubleshooting ### Connection Issues If you cannot connect to Corbits: 1. Verify the facilitator URL is correct 2. Check network connectivity 3. Ensure API credentials are valid 4. Review firewall settings ### Payment Failures Common causes and solutions: | Issue | Solution | | --------------------- | --------------------------------------- | | Invalid signature | Verify wallet configuration and signing | | Insufficient balance | Ensure payer has enough tokens | | Network mismatch | Check chain ID matches configuration | | Expired authorization | Increase `maxTimeoutSeconds` | ## Next Steps Alternative facilitator with advanced features Deploy your own facilitator infrastructure Protect endpoints with payment middleware Build clients that handle x402 payments ## Resources * [Corbits Documentation](https://docs.corbits.dev/facilitator/introduction/overview) * [x402 Protocol Specification](https://x402.org) *** This entity -- Corbits -- is deployed and activley supporting SKALE. These are 3rd party services that may have their own terms and conditions and privacy polices. Use these services at your own risk. AI and agents is a highly experimental space; the 3rd party software solutions may have bugs or be unaudited. You and your agents and your customers use all 3rd party services chosen at your own risk and per their terms. # Kobaru Source: https://docs.skale.space/cookbook/facilitators/using-kobaru Payment infrastructure for devs with reliability, control and speed [Kobaru](https://www.kobaru.io) enables machine-to-machine payments, allowing AI agents and automated systems to pay per request. With payment infrastructure built for developers they aim to deliver reliable, transparent payments without operational overhead. ## Kobaru Products * **Kobaru Console**: A central revenue command center that allows users to manage payment infrastructure, track real-time analytics, and perform professional financial reconciliation without writing code. * **Transparent Proxy**: A no-code monetization tool that transforms any existing API into a paid endpoint by wrapping it in a payment layer without requiring changes to the original backend. * **Kobaru Gateway**: The core facilitator infrastructure that handles cryptographic signatures and verifies instant settlements on the SKALE network for incoming payment requests. * **API Paywall Cookbook**: An open-source repository of production-ready templates and examples that help developers implement various monetization patterns across multiple programming languages. ## Why SKALE Base with Kobaru? Kobaru makes it easy to get started with x402 payments on SKALE. Just hook right up to the gateway with your existing SDK -- setting Kobaru as the facilitator -- and you can start accepting payments on SKALE. **x402 Facilitator** Point your SDK to `https://gateway.kobaru.io` and start accepting payments with SKALE. **Instant settlement.** SKALE's instant finality—faster than any other network. Your customers get immediate access, you get immediate revenue. **Predictable costs.** No gas price spikes or unexpected fees. Perfect for high-volume APIs processing thousands of micropayments daily. **Fiat Settlement.** Kobaru focuses on abstracting payment complexities and will soon introduce Fiat Settlement options for users looking to leverage SKALE's performance without any blockchain-related friction. *** ## Prerequisites * A EVM wallet address - to receive USDC payments * An API you want to monetize * Basic understanding of x402 protocol. To learn more about it check [here](/get-started/agentic-builders/start-with-x402). *** ## Instant API Monetization **Transparent Proxy**, a Kobaru product, transforms any existing API into a monetized endpoint without modifying your backend. No code changes. No deployment cycles. No infrastructure modifications. This is not just "zero code"—this is production-ready monetization that works with your existing infrastructure exactly as it runs today. ### Step 1: Create Your Kobaru Account 1. Go to [console.kobaru.io](https://console.kobaru.io) 2. Sign up with your email ### Step 2: Register Your API In the Kobaru Console: 1. Click **New Service** 2. Configure your service: * **Service name**: Descriptive name (e.g., `skale-data-api`) * **Backend URL**: Your API's base URL (e.g., `https://api.yourcompany.com`) * **Slug**: Unique identifier for proxy URL (e.g., `skale-data`) 3. Define your first paid route: * **Route pattern**: Endpoint path (e.g., `/premium-data`) * **Price**: Cost per request in USD (e.g., `$0.001`) * **Network**: Select **SKALE Base** (Mainnet or Sepolia) * **Usage model**: `pay_per_request` or `pay_per_time` ### Step 3: Go Live and Get Paid Your API is now accessible at: ``` https://access.kobaru.io/{your-slug}/premium-data ``` **That's it.** Your API is now x402-compatible an able to be paid for by both humans and agents using x402. Kobaru automatically: * Returns an HTTP 402 response with payment requirements on unpaid requests * Verifies payments on SKALE Base with cryptographic proof * Forwards authenticated requests to your backend * Settles payments to your wallet in \~1 second * Logs every transaction for reconciliation *** ## x402 SDK ### Official support **Languages supported:** * **TypeScript/Node.js** - Full middleware support for Hono and Express * **Go** - Integration for Go backends with Gin * **Python** - Flask and FastAPI compatibility **Key advantage:** Use the same SDK code across any x402-compatible facilitator. Kobaru implements the complete x402 v2 specification, ensuring your integration works exactly as documented. ### Integration Environment variables: ```bash theme={null} # Required: Your wallet to receive payments WALLET_ADDRESS=0xYourWalletAddress # Optional: Kobaru API key for enhanced features KOBARU_API_KEY=your_api_key_from_console ``` Here's a minimal example to accept SKALE payments with Kobaru: ```typescript theme={null} import { Hono } from "hono"; import { serve } from "@hono/node-server"; import { cors } from "hono/cors"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import "dotenv/config"; const app = new Hono(); app.use("*", cors()); async function main() { // Configure Kobaru facilitator const facilitatorUrl = "https://gateway.kobaru.io"; const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl }) // Register EVM payment scheme for SKALE const resourceServer = new x402ResourceServer(facilitatorClient); resourceServer.register("eip155:*", new ExactEvmScheme()); // Fetch token metadata from Kobaru instead of hardcoding const networkId = "eip155:324705682"; // SKALE Base Mainnet const assetAddress = "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD"; // USDC on SKALE Base // Define payment requirements const routes = { "GET /api/data": { accepts: [ { scheme: "exact", network: networkId, payTo: process.env.WALLET_ADDRESS as `0x${string}`, price: { amount: "1000", // 0.001 USDC asset: assetAddress, extra: { name: "USDC", version: "2" } } } ], description: "Premium data access", mimeType: "application/json" } }; // Apply payment middleware app.use("*", paymentMiddleware(routes, resourceServer)); // Protected endpoint app.get("/api/data", (c) => { return c.json({ message: "Premium data unlocked!", timestamp: new Date().toISOString() }); }); const port = 3000; serve({ fetch: app.fetch, port }, () => { console.log(`Server running on http://localhost:${port}`); console.log(`Using Kobaru facilitator: ${facilitatorUrl}`); }); } main().catch(console.error); ``` ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; import "dotenv/config"; async function main() { const account = privateKeyToAccount( process.env.PRIVATE_KEY as `0x${string}` ); const evmScheme = new ExactEvmScheme(account); const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); const url = "http://localhost:3000/api/data"; const response = await fetch(url); if (response.status === 402) { console.log("Payment required, processing via Kobaru..."); const responseBody = await response.json(); const paymentRequired = httpClient.getPaymentRequiredResponse( (name: string) => response.headers.get(name), responseBody ); const paymentPayload = await httpClient.createPaymentPayload( paymentRequired ); const paymentHeaders = httpClient.encodePaymentSignatureHeader( paymentPayload ); const paidResponse = await fetch(url, { headers: { ...paymentHeaders }, }); const data = await paidResponse.json(); console.log("Data received:", data); } } main(); ``` Kobaru maintains an open-source cookbook with production-grade examples demonstrating real-world integrations: **API Paywall Cookbook:** [https://github.com/kobaru-io/api-paywall-cookbook](https://github.com/kobaru-io/api-paywall-cookbook) *** ## Kobaru Console The Kobaru Console is not just a dashboard—it is a complete revenue command center that gives you operational control over your entire payment infrastructure. **Create a free account** to unlock Console access with enhanced features: * **Real-time analytics** - Live transaction feed, revenue tracking, settlement confirmation * **Failure diagnostics** - Identify and resolve payment issues before they impact customers * **Professional reconciliation** - Complete audit trails and exportable reports for compliance * **Manage your payment infrastructure without touching code** - Adjust pricing, endpoints, and rate limits instantly * **Improved performance** - Priority routing and enhanced uptime guarantees * **Enterprise support** - 24/7 diagnostics with guaranteed response times **Using Console with the SDK:** When you have an API key from [console.kobaru.io](https://console.kobaru.io), include it for enhanced service: ```typescript theme={null} const facilitator = new HTTPFacilitatorClient({ url: "https://gateway.kobaru.io", headers: { "Authorization": `Bearer ${process.env.KOBARU_API_KEY}` } }); ``` ## Troubleshooting Kobaru provides detailed error messages and the Console shows real-time diagnostics. For common issues: ### Payment verification fails | Issue | Solution | | -------------------- | -------------------------------------------------------------------------------------------------------------------- | | Invalid API key | Verify `KOBARU_API_KEY` in console.kobaru.io | | Wrong network | Ensure application uses `eip155:1187947933` (mainnet) or `eip155:324705682` (testnet) | | Insufficient balance | User needs USDC on SKALE Base (bridge from Base) | | Expired signature | Client must sign fresh payment (check `maxTimeoutSeconds`) | | Wrong token address | Use `0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20` (mainnet) or `0x2e08028E3C4c2356572E096d8EF835cD5C6030bD` (testnet) | ### Viem token metadata issues If using Viem with Node.js, token name fetching may fail. **Solution**: Fetch metadata from Kobaru's `/supported` endpoint instead of hardcoding: ```typescript theme={null} // Fetch from Kobaru instead of hardcoding const supported = await facilitatorClient.getSupported(); const networkData = supported.kinds?.find( (kind: any) => kind.network === networkId && kind.extra?.asset?.toLowerCase() === assetAddress.toLowerCase() ); if (networkData?.extra) { const { name, version, decimals } = networkData.extra; return { name: (name as string), version: (version as string), decimals: (decimals as number) }; } ``` *** ## Next steps Alternative hosted facilitator service Deploy your own facilitator infrastructure Protect endpoints with payment middleware Build clients that handle x402 payments *** ## Resources * [Kobaru Documentation](https://docs.kobaru.io) * [x402 Protocol Specification](https://x402.org) *** This entity -- Kobaru -- is deployed and activley supporting SKALE. These are 3rd party services that may have their own terms and conditions and privacy polices. Use these services at your own risk. AI and agents is a highly experimental space; the 3rd party software solutions may have bugs or be unaudited. You and your agents and your customers use all 3rd party services chosen at your own risk and per their terms. # PayAI Source: https://docs.skale.space/cookbook/facilitators/using-payai Integrate PayAI facilitator for x402 payment processing PayAI is a hosted facilitator service that simplifies x402 payment processing. Instead of running your own facilitator infrastructure, you can use PayAI's endpoints to handle payment verification and settlement. ## Prerequisites * Node.js and npm installed * A SKALE Chain endpoint * Basic understanding of x402 protocol ## Configuration ### Environment Variables Create a `.env` file with PayAI configuration: ```bash theme={null} # Facilitator Router Address RECEIVING_ADDRESS=0xFacilitatorReceivingAddress # Payment token configuration PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)" # SKALE network configuration NETWORK_CHAIN_ID=324705682 ``` ## Integration with PayAI ```typescript theme={null} import { Hono } from "hono"; import { serve } from "@hono/node-server"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import type { Network } from "@x402/core/types"; import "dotenv/config"; const app = new Hono(); async function main() { // Point to PayAI facilitator const facilitatorUrl = "https://facilitator.payai.network"; const receivingAddress = process.env.RECEIVING_ADDRESS as `0x${string}`; const paymentTokenAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`; const paymentTokenName = process.env.PAYMENT_TOKEN_NAME ; const networkChainId = process.env.NETWORK_CHAIN_ID; const network: Network = `eip155:${networkChainId}`; // Initialize PayAI facilitator client const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl }); const resourceServer = new x402ResourceServer(facilitatorClient); resourceServer.register("eip155:*", new ExactEvmScheme()); // Apply payment middleware app.use( paymentMiddleware( { "GET /api/data": { accepts: [ { scheme: "exact", network: network, payTo: receivingAddress, price: { amount: "10000", // 0.01 tokens asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1", }, }, }, ], description: "Premium data access", mimeType: "application/json", }, }, resourceServer ) ); // Protected endpoint app.get("/api/data", (c) => { return c.json({ message: "Premium data unlocked via PayAI!", timestamp: new Date().toISOString(), }); }); const port = 3000; serve({ fetch: app.fetch, port }, () => { console.log(`Server running on http://localhost:${port}`); console.log(`Using PayAI facilitator: ${facilitatorUrl}`); }); } main().catch(console.error); ``` ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; import "dotenv/config"; async function main() { // Setup wallet const account = privateKeyToAccount( process.env.PRIVATE_KEY as `0x${string}` ); // Create x402 client const evmScheme = new ExactEvmScheme(account); const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); // Access PayAI-protected resource const url = "http://localhost:3000/api/data"; const response = await fetch(url); if (response.status === 402) { console.log("Payment required, processing via PayAI..."); const responseBody = await response.json(); const paymentRequired = httpClient.getPaymentRequiredResponse( (name: string) => response.headers.get(name), responseBody ); const paymentPayload = await httpClient.createPaymentPayload( paymentRequired ); const paymentHeaders = httpClient.encodePaymentSignatureHeader( paymentPayload ); const paidResponse = await fetch(url, { headers: { ...paymentHeaders }, }); const data = await paidResponse.json(); console.log("Data received:", data); } } main(); ``` ## Troubleshooting ### Connection Issues If you cannot connect to PayAI: 1. Verify the facilitator URL is correct 2. Check network connectivity 3. Ensure API credentials are valid 4. Review firewall settings ### Payment Failures Common causes and solutions: | Issue | Solution | | --------------------- | --------------------------------------- | | Invalid signature | Verify wallet configuration and signing | | Insufficient balance | Ensure payer has enough tokens | | Network mismatch | Check chain ID matches configuration | | Expired authorization | Increase `maxTimeoutSeconds` | ## Next Steps Alternative facilitator with advanced features Deploy your own facilitator infrastructure Protect endpoints with payment middleware Build clients that handle x402 payments ## Resources * [PayAI Documentation](https://docs.payai.network/x402/supported-networks) * [x402 Protocol Specification](https://x402.org) *** This entity -- PayAI -- is deployed and activley supporting SKALE. These are 3rd party services that may have their own terms and conditions and privacy polices. Use these services at your own risk. AI and agents is a highly experimental space; the 3rd party software solutions may have bugs or be unaudited. You and your agents and your customers use all 3rd party services chosen at your own risk and per their terms. # RelAI Source: https://docs.skale.space/cookbook/facilitators/using-relai Integrate RelAI facilitator for x402 payment processing RelAI is a hosted facilitator service that simplifies x402 payment processing on SKALE Network. Instead of running your own facilitator infrastructure, you can use RelAI's SDK to handle payment verification and settlement across multiple tokens. ## Highlights The RelAI facilitator has **enabled BITE transactions** for both **SKALE Base** and **SKALE Base Sepolia**. **What does this mean?** * When a merchant settles any x402 transaction through RelAI facilitator it gets automatically encrypted through BITE and only decrypted at the consensus level For more inforamtion around BITE please check [here](concepts/bite-protocol/intro-bite-protocol). ## Prerequisites * Node.js and npm installed * A wallet on SKALE Base or SKALE Base Sepolia * Basic understanding of x402 protocol ## Configuration ### Environment Variables Create a `.env` file with RelAI configuration: ```bash theme={null} # Your wallet address to receive payments PAYMENT_RECEIVER_ADDRESS=0xYourWalletAddressHere # Private key for client (NEVER commit this!) PRIVATE_KEY=0xYourPrivateKeyHere # Server port PORT=3000 # Price for premium endpoint (in USD) PREMIUM_PRICE=0.01 ``` ### Supported Networks and Tokens RelAI supports the following SKALE networks: | Network | Tokens | | ------------------ | -------------------------- | | SKALE Base Sepolia | USDC, USDT, ETH, SKL, WBTC | | SKALE Base | USDC, USDT, ETH, SKL, WBTC | ## Integration with RelAI Make sure to install the RelAI SDK: ```sh theme={null} npm install @relai-fi/x402 ``` ```typescript theme={null} import express from 'express'; import cors from 'cors'; import dotenv from 'dotenv'; import Relai from '@relai-fi/x402/server'; dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; // Middleware app.use(cors()); app.use(express.json()); // Initialize RelAI SDK for SKALE Base Sepolia const relai = new Relai({ network: 'skale-base-sepolia', facilitatorUrl: 'https://facilitator.x402.fi' }); // Public endpoint - no payment required app.get('/api/public', (req, res) => { res.json({ message: 'This is a public endpoint - no payment required', network: 'SKALE Base Sepolia' }); }); // Premium endpoint - protected by x402 payment app.get( '/api/premium', relai.protect({ payTo: process.env.PAYMENT_RECEIVER_ADDRESS!, price: parseFloat(process.env.PREMIUM_PRICE || '0.01'), }), (req, res) => { // Payment is automatically verified by the middleware // req.payment contains the payment details res.json({ success: true, message: 'Premium data unlocked via RelAI!', payment: req.payment }); } ); // Start server app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); }); ``` ```typescript theme={null} import dotenv from 'dotenv'; import { createX402Client } from '@relai-fi/x402/client'; import { privateKeyToAccount } from 'viem/accounts'; dotenv.config(); async function main() { const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000'; // Create account from private key const privateKey = process.env.PRIVATE_KEY as `0x${string}`; const account = privateKeyToAccount(privateKey); // Create x402 client with RelAI const client = createX402Client({ wallets: { evm: { address: account.address, signTypedData: (params) => account.signTypedData(params as any), }, }, }); // Access RelAI-protected resource const response = await client.fetch(`${SERVER_URL}/api/premium`); if (response.ok) { const data = await response.json(); console.log('Data received:', data); } } main().catch(console.error); ``` ## Troubleshooting ### Connection Issues If you cannot connect to RelAI: 1. Verify the facilitator URL is correct 2. Check network connectivity 3. Ensure your wallet has tokens for gas fees 4. Review firewall settings ### Payment Failures Common causes and solutions: | Issue | Solution | | -------------------- | -------------------------------------------- | | Invalid signature | Verify wallet configuration and signing | | Insufficient balance | Ensure payer has enough tokens | | Network mismatch | Check network matches configuration | | Invalid receiver | Verify PAYMENT\_RECEIVER\_ADDRESS is correct | ## Next Steps Alternative facilitator with advanced features Deploy your own facilitator infrastructure Protect endpoints with payment middleware Build clients that handle x402 payments ## Resources * [RelAI Documentation](https://relai.fi/documentation) * [x402 Protocol Specification](https://x402.org) *** This entity -- RelAI -- is deployed and actively supporting SKALE. These are 3rd party services that may have their own terms and conditions and privacy policies. Use these services at your own risk. AI and agents is a highly experimental space; the 3rd party software solutions may have bugs or be unaudited. You and your agents and your customers use all 3rd party services chosen at your own risk and per their terms. # x402x Source: https://docs.skale.space/cookbook/facilitators/using-x402x Integrate x402x facilitator for advanced payment processing features x402x is an advanced facilitator service that extends the base x402 protocol with additional features for enterprise use cases. It provides enhanced payment processing, analytics, and customization options. ## Prerequisites * Node.js and npm installed * A SKALE Chain endpoint * Understanding of x402 protocol ## Configuration ### Environment Variables Create a `.env` file with x402x configuration: ```bash theme={null} # Facilitator router address RECEIVING_ADDRESS=0x1Ae0E196dC18355aF3a19985faf67354213F833D # Payment token configuration PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)" # Your receiving address RECEIVING_ADDRESS=0xyour_address_to_receive_payment # SKALE network configuration NETWORK_CHAIN_ID=324705682 ``` ## Basic Integration ```typescript theme={null} import { Hono } from "hono"; import { serve } from "@hono/node-server"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { registerExactEvmScheme } from "@x402/evm/exact/server"; import type { RouteConfig as X402RouteConfig } from "@x402/core/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import { registerRouterSettlement, registerSettlementHooks, createSettlementRouteConfig, } from "@x402x/extensions"; import "dotenv/config"; const app = new Hono(); const PORT = Number(process.env.PORT) || 3001; async function main() { app.use( "/*", cors({ origin: "*", credentials: false, exposeHeaders: ["PAYMENT-REQUIRED", "PAYMENT-RESPONSE"], allowHeaders: [ "Content-Type", "PAYMENT-SIGNATURE", "PAYMENT-RESPONSE", "X-PAYMENT-SIGNATURE", "X-PAYMENT-RESPONSE", ], }), ); const facilitatorUrl = "https://facilitator.x402x.dev"; const receivingAddress = process.env.RECEIVING_ADDRESS as `0x${string}`; const networkChainId = process.env.NETWORK_CHAIN_ID || "324705682"; const network = `eip155:${networkChainId}`; // Initialize x402x facilitator client with authentication const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl }); const resourceServer = new x402ResourceServer(facilitatorClient); registerExactEvmScheme(resourceServer, { networks: ["eip155:*"], }); registerRouterSettlement(resourceServer); registerSettlementHooks(resourceServer); await resourceServer.initialize(); const routes = { "GET /api/premium": createSettlementRouteConfig( { accepts: { scheme: "exact", network, payTo: receivingAddress, price: "$0.01", }, }, { // Dynamic fee: query facilitator /calculate-fee on 402 probe facilitatorUrl, }, ) as X402RouteConfig, }; app.use("/api/premium", paymentMiddleware(routes, resourceServer)); app.get("/api/premium", async (c) => { console.log("[Server] Weather request received"); return c.json({ message: "Premium content unlocked via x402x!", features: ["analytics", "webhooks", "rate-limiting"], timestamp: new Date().toISOString(), }); }); serve({ fetch: app.fetch, port }, () => { console.log(`Server running on http://localhost:${port}`); console.log(`Using x402x facilitator: ${facilitatorUrl}`); }); } main().catch(console.error); ``` ```typescript theme={null} import { wrapFetchWithPayment } from "x402-fetch"; import { x402Client } from "@x402/core/client"; import { registerX402xScheme } from "@x402x/extensions"; import { privateKeyToAccount } from "viem/accounts"; import "dotenv/config"; async function main() { const account = privateKeyToAccount( process.env.PRIVATE_KEY as `0x${string}` ); const baseUrl = process.env.BASE_URL || "http://localhost:3001"; const networkChainId = process.env.NETWORK_CHAIN_ID || "324705682"; const client = new x402Client(); const networkId = `eip155:${networkChainId}` as `${string}:${string}`; registerX402xScheme(client, "eip155:324705682", account); const fetchWithPayment = wrapFetchWithPayment(fetch, client); const fetchWithPayment = wrapFetchWithPayment( fetch, client ); const premium_url = `${baseUrl}/api/premium`; try{ const response = await fetchWithPayment(premium_url, { method: "GET", headers: { "Content-Type": "application/json", }, }); const data = await response.json(); console.log(data); } catch(error){ console.error( "[Client] Error:", error instanceof Error ? error.message : error ); process.exit(1); } const response = await fetchWithPayment("/api/premium-content", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ /* your data */ }), }); const result = await response.json(); } main(); ``` ## Troubleshooting ### Connection Issues If you cannot connect to x402x: 1. Verify the facilitator URL is correct 2. Check network connectivity 3. Ensure API credentials are valid 4. Review firewall settings ### Payment Failures Common causes and solutions: | Issue | Solution | | --------------------- | --------------------------------------- | | Invalid signature | Verify wallet configuration and signing | | Insufficient balance | Ensure payer has enough tokens | | Network mismatch | Check chain ID matches configuration | | Expired authorization | Increase `maxTimeoutSeconds` | ## Next Steps Alternative facilitator with advanced features Deploy your own facilitator Protect endpoints with payments Create payment-enabled agents ## Resources * [x402x Documentation](https://www.x402x.dev/docs) * [x402 Protocol Specification](https://x402.org) *** This entity -- x402x -- is deployed and activley supporting SKALE. These are 3rd party services that may have their own terms and conditions and privacy polices. Use these services at your own risk. AI and agents is a highly experimental space; the 3rd party software solutions may have bugs or be unaudited. You and your agents and your customers use all 3rd party services chosen at your own risk and per their terms. # Generating Random Numbers in Solidity Source: https://docs.skale.space/cookbook/native-features/rng-get-random-number Generate random numbers in Solidity with SKALE Native RNG This recipe explores generating a random number on SKALE without the use of a 3rd-party provider like Chainlink and without callbacks. The actual random value is generating from the signature of the block signature which is randomized depending on which entities active in validation of the chain sign the actual block. ## Using RNG ### Using SKALE RNG Library The easiest way is to use the SKALE RNG library: #### Step 1: Install Library ```shell theme={null} npm install @dirtroad/skale-rng ``` #### Step 2: Import and Use ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@dirtroad/skale-rng/contracts/RNG.sol"; contract MyRandomContract is RNG { /** * Generate a random number between 0 and 2^256-1 */ function generateRandom() external view returns (uint256) { return getRandomNumber(); } /* * Generate a random number between min and max */ function generateRandomInRange(uint256 min, uint256 max) external view returns (uint256) { return getNextRandomRange(min, max); } } ``` ### Direct Precompiled Contract Call Use inline assembly to call the precompiled contract at address `0x18`: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract GetRandomNumber { function getRandom() public view returns (bytes32) { bytes32 randomValue; assembly { let freemem := mload(0x40) let start_addr := add(freemem, 0) if iszero(staticcall(gas(), 0x18, 0, 0, start_addr, 32)) { invalid() } randomValue := mload(freemem) } return randomValue; } function getRandomUint256() public view returns (uint256) { return uint256(getRandom()); } } ``` *** ## Coin Flip Implementation A coin flip randomly selects between two outcomes: * **Heads** (true or 1) * **Tails** (false or 0) ### Step 1: Install Library ```shell theme={null} npm install @dirtroad/skale-rng ``` ### Basic Coin Flip Using the SKALE RNG library makes it simpler: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@dirtroad/skale-rng/contracts/RNG.sol"; contract CoinFlipWithLibrary is RNG { enum CoinSide { Heads, Tails } event CoinFlipped(address indexed player, CoinSide result); function flip() public returns (CoinSide) { uint256 random = getRandomNumber(); CoinSide result = (random % 2 == 0) ? CoinSide.Heads : CoinSide.Tails; emit CoinFlipped(msg.sender, result); return result; } } ``` ### Basic Coin Flip Here's a simple coin flip contract using inline assembly to call the RNG precompile: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract CoinFlip { enum CoinSide { Heads, Tails } event CoinFlipped(address indexed player, CoinSide result); function flip() public returns (CoinSide) { uint256 random = getRandomNumber(); CoinSide result = (random % 2 == 0) ? CoinSide.Heads : CoinSide.Tails; emit CoinFlipped(msg.sender, result); return result; } function getRandomNumber() private view returns (uint256) { bytes32 randomBytes; assembly { let freemem := mload(0x40) if iszero(staticcall(gas(), 0x18, 0, 0, freemem, 32)) { invalid() } randomBytes := mload(freemem) } return uint256(randomBytes); } } ``` RNG is native to SKALE and relies on SKALE's Consensus. In local testing or on other chains, this will always return 0. Random numbers are generated per block. Multiple calls in the same block will return the same value unless manipulated through bitwise or other mathemtical operations. ## Resources * [SKALE RNG Library](https://www.npmjs.com/package/@dirtroad/skale-rng) # Send Gasless Transactions Source: https://docs.skale.space/cookbook/native-features/send-gasless-transaction Recipe for sending gasless transactions on SKALE SKALE offers native gasless transactions that allow users to send transactions without needing to hold gas tokens. This is achieved through SKALE's Proof-of-Work mechanism that replaces traditional gas payments with computational work done client-side. For more information about SKALE's gasless transaction technology, see the [SKALE Gasless Transactions documentation](https://docs.skale.space/building-applications/gas-fees/#skale-gasless-transactions). This guide demonstrates how to implement gasless transactions using the `@dirtroad/gasless` library and [viem](https://viem.sh). ## Installation ```bash TypeScript theme={null} # NPM npm install viem @dirtroad/gasless # Yarn yarn add viem @dirtroad/gasless # PNPM pnpm add viem @dirtroad/gasless # Bun bun add viem @dirtroad/gasless ``` ```bash Flutter/Dart theme={null} dart pub add skale ``` ```bash Rust theme={null} cargo add gasless ``` *** ## Example ```typescript typescript.ts theme={null} import { mineGasForTransaction } from "@dirtroad/gasless"; import { createPublicClient, createWalletClient, http } from "viem"; import { skaleCalypsoTestnet } from "viem/chains"; import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; async function main() { const privateKey = generatePrivateKey(); const client = createPublicClient({ chain: skaleCalypsoTestnet, transport: http() }); const wallet = createWalletClient({ chain: skaleCalypsoTestnet, transport: http(), account: privateKeyToAccount(privateKey) }); // Generate gasless transaction parameters const { gasPrice } = await mineGasForTransaction(100_000, wallet.account.address, 0); // Send transaction with computed gas price const transactionHash = await wallet.sendTransaction({ to: "0x62Fe932FF26e0087Ae383f6080bd2Ed481bA5A8A", data: `0x0c11dedd000000000000000000000000${wallet.account.address.substring(2)}`, gas: BigInt(100_000), gasPrice: BigInt(gasPrice) }); const receipt = await client.waitForTransactionReceipt({ hash: transactionHash }); console.log("Gasless transaction receipt: ", receipt); return receipt; } main().catch(console.error); ``` ```dart dart.dart theme={null} import 'package:skale/skale.dart'; Future main() async { final skale = Skale(); // Generate gasless transaction final tx = await skale.sendGaslessTransaction( to: '0x62Fe932FF26e0087Ae383f6080bd2Ed481bA5A8A', data: '0x0c11dedd...', gasLimit: 100000, ); print('Transaction sent: ${tx.hash}'); } ``` ```rust rust.rs theme={null} use gasless::mine_gas_for_transaction; #[tokio::main] async fn main() -> Result<(), Box> { let gas_amount = 100_000; let address = "0x62Fe932FF26e0087Ae383f6080bd2Ed481bA5A8A".to_string(); let nonce = 0; // Mine gas for transaction let result = mine_gas_for_transaction(gas_amount, address, nonce).await?; println!("Gas Price: {}", result.gas_price); println!("Mining duration: {} ms", result.duration); // Use the gas price in your transaction // The result.gas_price can be used with any Web3 library Ok(()) } ``` *** *** ## Libraries & Resources * **[@dirtroad/gasless](https://www.npmjs.com/package/@dirtroad/gasless)** - JavaScript/TypeScript library for gasless transactions * **[skale.dart](https://pub.dev/packages/skale)** - Dart/Flutter library for SKALE integration * **[SKALE Gasless SDK Documentation](https://dirtroad.dev/sdks/skale-gasless-sdk)** - Comprehensive SDK documentation * **[crates.io](https://crates.io/crates/gasless)** - Rust implementation * **[SKALE Gasless Transactions](https://docs.skale.space/building-applications/gas-fees/#skale-gasless-transactions)** - SKALE gasless transaction documentation # Conditional Transactions Source: https://docs.skale.space/cookbook/privacy/conditional-transactions Complete step-by-step guide to implementing a private Rock-Paper-Scissors game using BITE Conditional Transactions ## Rock-Paper-Scissors with Conditional Transactions This comprehensive tutorial walks you through building a **Rock-Paper-Scissors game** that uses BITE's Conditional Transaction (CTX) feature to keep player moves encrypted until both players have committed. The game automatically decrypts moves on-chain and determines the winner without any manual reveal phase. In a traditional Rock-Paper-Scissors game, if Player 1 submits their move first, Player 2 could see it and counter. Using CTX, both moves remain encrypted until both players commit. The network then automatically decrypts them in the next block, ensuring a fair game. ## How the Game Works The game implements a **commit-reveal pattern with automatic decryption** using BITE CTX: **Game Flow:** | Phase | Description | Key Action | | -------------- | ------------------------------------------------------ | -------------------------------------------- | | **1. Create** | Player 1 creates a game with encrypted move and wager | Move encrypted via BITE SDK, stored on-chain | | **2. Join** | Player 2 joins with encrypted move and CTX gas payment | CTX automatically submitted for decryption | | **3. Decrypt** | Network decrypts both moves in next block | `onDecrypt` callback triggered automatically | | **4. Resolve** | Contract determines winner and distributes funds | Winner gets 2x wager, draw = refunds | This tutorial requires access to a CTX-enabled SKALE chain. The demo is configured for **BITE V2 Sandbox 2**. Check [BITE Protocol Phases](/concepts/bite-protocol/phases) for current availability. **Prerequisites:** * Node.js 18+ and npm/yarn * Wallet browser extension * Access to a CTX-enabled SKALE chain * Basic knowledge of Solidity and React *** ## Step 1: Clone the Project Clone the Rock-Paper-Scissors demo repository: ```bash theme={null} # Clone the repository git clone -b thegreataxios/rps https://github.com/TheGreatAxios/ctxs.git # Navigate into the project cd ctxs/rock-paper-scissors ``` **Install Dependencies & Build:** ```bash theme={null} # Install Foundry dependencies forge install # Build contracts forge build ``` ```bash theme={null} # Install frontend dependencies cd frontend npm install ``` **Deploy Contracts:** The deployment script deploys both the MockSKL token and the RockPaperScissors game contract. **Update Addresses:** After deploying, copy the output addresses to your frontend `config/contracts.ts`. ```bash theme={null} # Set your environment variables export RPC_URL="https://base-sepolia-testnet.skalenodes.com/v1/bite-v2-sandbox-2" export PRIVATE_KEY="0x..." # Your wallet private key # From the rock-paper-scissors/contracts directory cd contracts # Deploy both MockSKL and RockPaperScissors forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast ``` **Expected Output:** ``` MockSKL deployed at: 0x... RockPaperScissors deployed at: 0x... Deployer: 0x... Network: 103698795 === UPDATE FRONTEND === TOKEN_ADDRESS[chainId] = 0x... CONTRACT_ADDRESS[chainId] = 0x... ``` **Fund Your Wallet:** After deployment, the script outputs the token address. Use a block explorer or cast to call `mint()` on the MockSKL contract to get tokens for testing. **Project Structure:** ``` rock-paper-scissors/ ├── contracts/ # Solidity smart contracts │ ├── src/ │ │ ├── RockPaperScissors.sol # Main game contract │ │ └── encryption/ │ │ ├── Precompiled.sol # BITE V2 precompile library │ │ └── types.sol # Type definitions │ └── test/ # Foundry tests ├── frontend/ # React + Vite frontend │ ├── src/ │ │ ├── components/ # React components │ │ ├── config/ # Configuration files │ │ ├── hooks/ # Custom React hooks │ │ └── utils/ # Utility functions │ └── package.json └── README.md ``` *** ## Step 2: Smart Contract Overview The core game logic is in `RockPaperScissors.sol`. Let's break down the key components. **Game State Structure:** ```solidity theme={null} enum Move { None, Rock, Paper, Scissors } enum GameState { Created, Joined, Finished, Expired } struct Game { address player1; address player2; bytes encryptedMove1; // Encrypted move from Player 1 bytes encryptedMove2; // Encrypted move from Player 2 uint8 move1; // Decrypted move (revealed after CTX) uint8 move2; // Decrypted move (revealed after CTX) uint256 wagerAmount; address wagerToken; GameState state; uint256 joinDeadline; address winner; } ``` **BITE Precompile Addresses:** The contract imports `Precompiled.sol` to interact with BITE's precompiled contracts: ```solidity theme={null} import "./encryption/Precompiled.sol"; contract RockPaperScissors is ReentrancyGuard { using SafeERC20 for IERC20; // BITE Precompile addresses address public constant SUBMIT_CTX = 0x000000000000000000000000000000000000001B; address public constant ENCRYPT_TE = 0x000000000000000000000000000000000000001c; uint256 public constant CTX_GAS_LIMIT = 2500000; // 2.5M gas limit for CTX uint256 public constant CTX_GAS_PAYMENT = 0.06 ether; // Cost for CTX execution // ... } ``` **Precompile Addresses:** * `SUBMIT_CTX (0x...1B)`: Submits encrypted data for automatic decryption * `ENCRYPT_TE (0x...1C)`: Encrypts data using threshold encryption (not used directly - frontend encrypts) **Creating a Game:** ```solidity theme={null} function createGame(bytes calldata _encryptedMove, uint256 _wagerAmount, address _wagerToken) external nonReentrant returns (uint256 gameId) { require(_encryptedMove.length > 0, "Invalid encrypted move"); require(_wagerToken != address(0), "Must use ERC20 token"); // Transfer wager tokens from player to contract IERC20(_wagerToken).safeTransferFrom(msg.sender, address(this), _wagerAmount); gameId = nextGameId++; games[gameId] = Game({ player1: msg.sender, player2: address(0), encryptedMove1: _encryptedMove, encryptedMove2: "", move1: 0, move2: 0, wagerAmount: _wagerAmount, wagerToken: _wagerToken, state: GameState.Created, joinDeadline: block.timestamp + JOIN_TIMEOUT, winner: address(0) }); emit GameCreated(gameId, msg.sender, _wagerAmount, _wagerToken); } ``` **What happens:** 1. Player 1's encrypted move is stored on-chain 2. Wager tokens are transferred to the contract 3. Game enters `Created` state with a 1-hour join deadline **Joining a Game & Submitting CTX:** ```solidity theme={null} function joinGame(uint256 _gameId, bytes calldata _encryptedMove) external payable nonReentrant { Game storage game = games[_gameId]; require(game.player1 != address(0), "Game not found"); require(game.player2 == address(0), "Game full"); require(game.state == GameState.Created, "Invalid state"); require(block.timestamp <= game.joinDeadline, "Expired"); require(_encryptedMove.length > 0, "Invalid encrypted move"); require(msg.value == CTX_GAS_PAYMENT, "Invalid CTX gas payment"); // Transfer wager tokens from player to contract IERC20(game.wagerToken).safeTransferFrom(msg.sender, address(this), game.wagerAmount); game.player2 = msg.sender; game.encryptedMove2 = _encryptedMove; game.state = GameState.Joined; emit GameJoined(_gameId, msg.sender, _encryptedMove); // Submit CTX to decrypt both moves _submitDecryptCTX(_gameId); } ``` **Submitting the CTX:** ```solidity theme={null} function _submitDecryptCTX(uint256 _gameId) internal { Game storage game = games[_gameId]; // Prepare encrypted args (both moves) bytes[] memory encryptedArgs = new bytes[](2); encryptedArgs[0] = game.encryptedMove1; encryptedArgs[1] = game.encryptedMove2; // Prepare plaintext args (game ID for callback) bytes[] memory plaintextArgs = new bytes[](1); plaintextArgs[0] = abi.encode(_gameId); // Get CTX sender address and transfer gas payment address payable ctxSender = Precompiled.submitCTX( SUBMIT_CTX, CTX_GAS_LIMIT, encryptedArgs, plaintextArgs ); // Transfer gas payment to CTX sender payable(ctxSender).transfer(CTX_GAS_PAYMENT); } ``` **What happens:** 1. Both encrypted moves are packaged into `encryptedArgs` 2. The Game ID is passed as plaintext for callback identification 3. `Precompiled.submitCTX()` returns the CTX sender address 4. 0.06 ETH is transferred to cover CTX execution costs **Handling Decryption Callback:** ```solidity theme={null} function onDecrypt(bytes[] calldata decryptedArguments, bytes[] calldata plaintextArguments) external nonReentrant { // Decode game ID from plaintext args uint256 gameId = abi.decode(plaintextArguments[0], (uint256)); Game storage game = games[gameId]; require(game.player1 != address(0), "Game not found"); require(game.state == GameState.Joined, "Invalid state"); // Decrypt moves from BITE game.move1 = uint8(bytes1(decryptedArguments[0])); game.move2 = uint8(bytes1(decryptedArguments[1])); require(game.move1 >= 1 && game.move1 <= 3, "Invalid move1"); require(game.move2 >= 1 && game.move2 <= 3, "Invalid move2"); emit MovesDecrypted(gameId, game.move1, game.move2); // Resolve game _resolveGame(gameId); } ``` **Resolving the Game:** ```solidity theme={null} function _resolveGame(uint256 _gameId) internal { Game storage game = games[_gameId]; Move move1 = Move(game.move1); Move move2 = Move(game.move2); if (move1 == move2) { // Draw - refund both game.winner = address(0); _refundPlayer(game.player1, game.wagerAmount, game.wagerToken); _refundPlayer(game.player2, game.wagerAmount, game.wagerToken); } else if ( (move1 == Move.Rock && move2 == Move.Scissors) || (move1 == Move.Paper && move2 == Move.Rock) || (move1 == Move.Scissors && move2 == Move.Paper) ) { // Player 1 wins game.winner = game.player1; _transferPayout(game.player1, game.wagerAmount * 2, game.wagerToken); } else { // Player 2 wins game.winner = game.player2; _transferPayout(game.player2, game.wagerAmount * 2, game.wagerToken); } game.state = GameState.Finished; emit GameFinished(_gameId, game.winner); } ``` *** ## Step 3: Frontend Setup **Install Dependencies:** Navigate to the frontend directory and install dependencies: ```bash theme={null} cd frontend npm install ``` The frontend uses the `@skalenetwork/bite` package for encryption: ```bash theme={null} npm install @skalenetwork/bite ``` **Frontend Project Structure:** ``` frontend/ ├── src/ │ ├── components/ # React components │ │ ├── CreateGame.tsx # Game creation form │ │ ├── JoinGame.tsx # Game joining form │ │ ├── GameView.tsx # Game state display │ │ └── GameList.tsx # Browse games │ ├── config/ │ │ └── contract.ts # Contract addresses & ABI │ ├── hooks/ │ │ └── useGameState.ts # Custom hook for game state │ ├── utils/ │ │ └── encryption.ts # Encryption utilities │ ├── App.tsx # Main app component │ └── main.tsx # Entry point └── package.json ``` **Key Files:** * [`CreateGame.tsx`](https://github.com/TheGreatAxios/ctxs/blob/thegreataxios/rps/rock-paper-scissors/frontend/src/components/CreateGame.tsx) - Handles game creation with move encryption * [`JoinGame.tsx`](https://github.com/TheGreatAxios/ctxs/blob/thegreataxios/rps/rock-paper-scissors/frontend/src/components/JoinGame.tsx) - Handles game joining with CTX gas payment * [`config/contract.ts`](https://github.com/TheGreatAxios/ctxs/blob/thegreataxios/rps/rock-paper-scissors/frontend/src/config/contract.ts) - Contract configuration * [`App.tsx`](https://github.com/TheGreatAxios/ctxs/blob/thegreataxios/rps/rock-paper-scissors/frontend/src/App.tsx) - Main application layout *** ## Step 4: CreateGame Component The `CreateGame.tsx` component handles game creation. Here are the key parts: **BITE SDK Initialization:** ```tsx theme={null} import { BITE } from '@skalenetwork/bite' // Encrypt move using BITE const encryptMove = async (move: Move): Promise => { // Convert move number to hex (1 -> "01", 2 -> "02", 3 -> "03") const moveHex = move.toString(16).padStart(2, '0') const rpcUrl = 'https://base-sepolia-testnet.skalenodes.com/v1/bite-v2-sandbox-2' const bite = new BITE(rpcUrl) const encryptedMove = await bite.encryptMessage(moveHex) return encryptedMove } ``` **Encryption Flow:** 1. Move value (1-3) is converted to hex: `move.toString(16).padStart(2, '0')` 2. BITE SDK encrypts the hex string using threshold encryption 3. Encrypted bytes are returned as a hex string for contract submission **Creating the Game:** ```tsx theme={null} const handleCreateGame = async () => { if (!selectedMove || !tokenAddress) return const amount = wagerAmount ? parseEther(wagerAmount) : BigInt(0) const needsApproval = allowance !== undefined && amount > (allowance || 0n) try { // Step 1: Approve token spending if needed if (needsApproval) { setIsApproving(true) const approveHash = await writeContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'approve', args: [contractAddress, amount], }) if (approveHash && publicClient) { await publicClient.waitForTransactionReceipt({ hash: approveHash }) await refetchAllowance() } setIsApproving(false) } // Step 2: Encrypt the selected move setIsEncrypting(true) const encryptedMove = await encryptMove(selectedMove) setIsEncrypting(false) // Step 3: Create game with encrypted move setIsCreating(true) const hash = await writeContract({ address: contractAddress, abi: CONTRACT_ABI, functionName: 'createGame', args: [encryptedMove, amount, tokenAddress], }) setCreateTxHash(hash) setIsCreating(false) } catch (e) { console.error('Error:', e) setIsApproving(false) setIsEncrypting(false) setIsCreating(false) } } ``` **Flow Summary:** 1. Check if token approval is needed (allowance vs wager amount) 2. If needed, approve the contract to spend tokens 3. **Encrypt the move** using BITE SDK 4. Call `createGame` with encrypted move, wager amount, and token address *** ## Step 5: JoinGame Component The `JoinGame.tsx` component handles joining an existing game. Key differences from `CreateGame`: **Fetching Game Details:** ```tsx theme={null} // Get game details for wager amount const { data: gameData } = useReadContract({ address: contractAddress, abi: CONTRACT_ABI, functionName: 'getGame', args: gameId ? [BigInt(gameId)] : undefined, query: { enabled: !!gameId, }, }) as { data: GameData | undefined } const wagerAmount = gameData?.wagerAmount ``` **Joining with CTX Gas Payment:** ```tsx theme={null} // CTX gas payment amount (0.06 ETH) - matches contract CTX_GAS_PAYMENT const CTX_GAS_PAYMENT = BigInt(60000000000000000) // 0.06 ether for 2.5M gas limit const handleJoinGame = async () => { if (!selectedMove || !gameId || !wagerAmount || !tokenAddress) return try { // Step 1: Approve token spending if needed const needsApproval = allowance !== undefined && wagerAmount > (allowance || 0n) if (needsApproval) { setIsApproving(true) const approveHash = await writeContract({ address: tokenAddress, abi: ERC20_ABI, functionName: 'approve', args: [contractAddress, wagerAmount], }) if (approveHash && publicClient) { await publicClient.waitForTransactionReceipt({ hash: approveHash }) await refetchAllowance() } setIsApproving(false) } // Step 2: Encrypt the selected move setIsEncrypting(true) const encryptedMove = await encryptMove(selectedMove) setIsEncrypting(false) // Step 3: Join game with CTX gas payment setIsJoining(true) const joinHash = await writeContract({ address: contractAddress, abi: CONTRACT_ABI, functionName: 'joinGame', args: [BigInt(gameId), encryptedMove], value: CTX_GAS_PAYMENT, // 0.06 ETH for CTX execution }) setJoinTxHash(joinHash) setIsJoining(false) } catch (e) { console.error('Error:', e) setIsApproving(false) setIsEncrypting(false) setIsJoining(false) } } ``` **Key Difference:** The `joinGame` call includes `value: CTX_GAS_PAYMENT` (0.06 ETH) which is required for the CTX execution. *** ## Step 6: Run the Application ```bash theme={null} npm run dev ``` Open your browser to `http://localhost:5173` and: 1. **Connect Wallet** - Connect your wallet to the BITE V2 Sandbox 2 network 2. **Get Test Tokens** - Visit the faucet to get gas tokens 3. **Play a Game** - Select your move, set wager amount, and create *** **Resources:** * **BITE TypeScript SDK**: [GitHub Repository](https://github.com/skalenetwork/bite-ts) * **Demo Repository**: [TheGreatAxios/ctxs](https://github.com/TheGreatAxios/ctxs/tree/thegreataxios/rps) * **BITE Protocol**: [Introduction](/concepts/bite-protocol/intro-bite-protocol) # Encrypted Transactions Source: https://docs.skale.space/cookbook/privacy/encrypted-transactions Complete step-by-step guide to implementing private blockchain transactions using BITE threshold encryption ## Sending Encrypted Transactions with BITE This comprehensive tutorial walks you through building a complete encrypted transaction system using BITE (Blockchain Integrated Threshold Encryption). You'll learn how to create private transfers that remain encrypted until consensus finality, preventing MEV attacks while maintaining blockchain transparency. This tutorial requires access to a BITE-enabled SKALE chain. Check [BITE Protocol Phases](/concepts/bite-protocol/phases) for current availability. ## What You'll Build A full-featured encrypted transfer application that: * Encrypts transaction data (recipient, amount) before blockchain submission * Shows real-time pending encrypted transactions * Displays decrypted results after consensus finality * Handles committee rotations seamlessly * Provides complete privacy for sensitive transfers ### Prerequisites * Node.js 16+ and npm/yarn * MetaMask browser extension * Access to a BITE-enabled SKALE chain * Basic knowledge of JavaScript and smart contracts ## Step 0: Setup BITE SDK If you haven't already, install the BITE TypeScript SDK: ```bash theme={null} npm i @skalenetwork/bite ethers dotenv ``` ## Step 1: Smart Contract Setup First, let's create a simple smart contract for our encrypted transfers. ### Create the Transfer Contract Create a new file `contracts/SimpleTransferDemo.sol`: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleTransferDemo { mapping(address => uint256) public balances; event Transferred(address indexed from, address indexed to, uint256 amount); constructor() { balances[msg.sender] = 1_000_000; } function allocate(address user, uint256 amount) external { balances[user] += amount; } function transfer(address to, uint256 amount) external { require(balances[msg.sender] >= amount, "Not enough balance"); balances[msg.sender] -= amount; balances[to] += amount; emit Transferred(msg.sender, to, amount); } function balanceOf(address user) external view returns (uint256) { return balances[user]; } } ``` **Contract Features:** * **Simple ERC20-like interface** for easy testing * **Initial funding** of 1,000,000 tokens to deployer * **Allocate function** for funding test accounts * **Transfer event** for tracking encrypted operations ### Deploy the Contract Use Foundry or Hardhat to deploy the contract to your BITE-enabled SKALE chain. See [ERC20 Deployment Guide](/cookbook/smart-contracts/deploy-erc20-token) for detailed deployment instructions. ## Step 2: Project Setup Let's set up our frontend application structure. ### Initialize the Project ```bash theme={null} # Create project directory mkdir bite-encrypted-transfers cd bite-encrypted-transfers # Initialize npm project npm init -y # Install dependencies npm install @skalenetwork/bite ethers dotenv vite npm install --save-dev vite @vitejs/plugin-react # Create basic structure mkdir src touch src/main.js src/style.css index.html .env package.json ``` ### Configure Environment Create `.env` file with your configuration: ```bash theme={null} VITE_EXPLORER_URL=https://explorer.skale.network/ VITE_SCHAIN_ENDPOINT=https://your-bite-chain.skale.network/ VITE_TRANSFER_CONTRACT=0xYourDeployedContractAddress ``` **Environment Variables:** * `VITE_EXPLORER_URL`: Block explorer for transaction links * `VITE_SCHAIN_ENDPOINT`: BITE-enabled SKALE chain RPC * `VITE_TRANSFER_CONTRACT`: Address of deployed transfer contract ### Update package.json ```json theme={null} { "name": "bite-encrypted-transfers", "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview" }, "dependencies": { "@skalenetwork/bite": "^0.7.0", "ethers": "^6.13.5" }, "devDependencies": { "vite": "^6.3.1" } } ``` ## Step 3: Core Implementation Let's build the main application logic. ### Initialize BITE Create `src/main.js`: ```javascript theme={null} import { ethers } from 'ethers'; import { BITE } from '@skalenetwork/bite'; // Configuration const SCHAIN_ENDPOINT = import.meta.env.VITE_SCHAIN_ENDPOINT; const TRANSFER_CONTRACT_ADDRESS = import.meta.env.VITE_TRANSFER_CONTRACT; const EXPLORER_URL = import.meta.env.VITE_EXPLORER_URL; // Initialize BITE const bite = new BITE(SCHAIN_ENDPOINT); // Contract ABI for our transfer function const TRANSFER_ABI = [ 'function transfer(address to, uint256 amount)', 'function balanceOf(address user) view returns (uint256)', 'event Transferred(address indexed from, address indexed to, uint256 amount)' ]; let currentAccount = null; let provider = null; ``` ### Transaction Data Encoding ```javascript theme={null} function encodeTransferData(recipient, amount) { const iface = new ethers.Interface(TRANSFER_ABI); return iface.encodeFunctionData('transfer', [recipient, amount]); } function parseTransferData(data) { const iface = new ethers.Interface(TRANSFER_ABI); try { const parsed = iface.parseTransaction({ data }); return { to: parsed.args[0], amount: parsed.args[1].toString() }; } catch (error) { return null; } } ``` ### Wallet Connection ```javascript theme={null} async function connectWallet() { if (typeof window.ethereum === 'undefined') { throw new Error('MetaMask not detected'); } try { // Request account access const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); currentAccount = accounts[0]; provider = new ethers.BrowserProvider(window.ethereum); // Update UI document.getElementById('walletAddress').textContent = currentAccount; document.getElementById('connectBtn').style.display = 'none'; document.getElementById('transferForm').style.display = 'block'; // Load initial balance await loadBalance(); } catch (error) { console.error('Wallet connection failed:', error); alert('Failed to connect wallet'); } } ``` ### Encrypted Transaction Submission ```javascript theme={null} async function sendEncryptedTransfer(recipient, amount) { if (!currentAccount) { throw new Error('Wallet not connected'); } try { // Encode the transfer function call const encodedData = encodeTransferData(recipient, ethers.parseEther(amount)); // Create transaction parameters const txParams = { from: currentAccount, to: TRANSFER_CONTRACT_ADDRESS, value: '0x0', data: encodedData, gasLimit: 300000, // Important: set manually for BITE transactions }; // Encrypt transaction data using BITE const encryptedParams = await bite.encryptTransaction(txParams); console.log('Encrypted transaction:', encryptedParams); // Submit encrypted transaction const txHash = await window.ethereum.request({ method: 'eth_sendTransaction', params: [encryptedParams], }); console.log('Transaction submitted:', txHash); // Add to pending transactions addPendingTransaction(txHash, recipient, amount); // Monitor for finality waitForTransactionFinality(txHash); return txHash; } catch (error) { console.error('Transfer failed:', error); alert(`Transfer failed: ${error.message}`); } } ``` ### Transaction Finality Monitoring ```javascript theme={null} async function waitForTransactionFinality(txHash) { const maxAttempts = 60; // 60 seconds timeout let attempts = 0; while (attempts < maxAttempts) { try { // Check transaction receipt const receipt = await provider.getTransactionReceipt(txHash); if (receipt && receipt.status === 1) { // Transaction confirmed, wait a bit more for decryption await new Promise(resolve => setTimeout(resolve, 3000)); // Try to get decrypted data try { const decryptedData = await bite.getDecryptedTransactionData(txHash); if (decryptedData) { // Successfully decrypted! const transferInfo = parseTransferData(decryptedData.data); addDecryptedResult(txHash, transferInfo); removePendingTransaction(txHash); await loadBalance(); // Update balance return; } } catch (decryptError) { console.log('Decryption not ready yet:', decryptError.message); } } } catch (error) { console.log('Transaction not ready yet...'); } attempts++; await new Promise(resolve => setTimeout(resolve, 1000)); } // Timeout occurred removePendingTransaction(txHash); alert('Transaction confirmation timeout. Please check the block explorer.'); } ``` ## Step 4: User Interface Let's create a user-friendly interface for our encrypted transfers. ### HTML Structure Create `index.html`: ```html theme={null} BITE Encrypted Transfers

BITE Encrypted Transfers

Private blockchain transactions with threshold encryption

Pending Encrypted Transactions

No pending transactions

Completed Transfers

No completed transfers

``` ### CSS Styling Create `src/style.css`: ```css theme={null} * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; color: #333; background: #f8fafc; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } header { text-align: center; margin-bottom: 40px; padding: 40px 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 12px; } header h1 { font-size: 2.5rem; margin-bottom: 10px; } header p { font-size: 1.1rem; opacity: 0.9; } section { background: white; padding: 30px; margin-bottom: 30px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.07); } .form-group { margin-bottom: 20px; } .form-group label { display: block; margin-bottom: 8px; font-weight: 600; color: #4a5568; } .form-group input { width: 100%; padding: 12px; border: 2px solid #e2e8f0; border-radius: 8px; font-size: 1rem; transition: border-color 0.2s; } .form-group input:focus { outline: none; border-color: #667eea; } button { background: #667eea; color: white; border: none; padding: 14px 28px; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: all 0.2s; } button:hover { background: #5a67d8; transform: translateY(-2px); } .transaction-list { max-height: 300px; overflow-y: auto; } .transaction-item { background: #f7fafc; padding: 16px; margin-bottom: 12px; border-radius: 8px; border-left: 4px solid #667eea; } .transaction-item.pending { border-left-color: #f6ad55; background: #fffbf0; } .transaction-item.completed { border-left-color: #48bb78; background: #f0fff4; } .transaction-link { display: inline-block; margin-top: 8px; color: #667eea; text-decoration: none; font-size: 0.9rem; } .transaction-link:hover { text-decoration: underline; } .empty-state { text-align: center; color: #718096; padding: 40px; font-style: italic; } .tx-hash { font-family: monospace; font-size: 0.9rem; color: #4a5568; margin-top: 4px; } @media (max-width: 768px) { .container { padding: 10px; } header h1 { font-size: 2rem; } section { padding: 20px; } } ``` ### UI Management Functions Add these functions to `src/main.js`: ```javascript theme={null} // Transaction tracking let pendingTransactions = new Map(); let completedTransactions = []; function addPendingTransaction(txHash, recipient, amount) { const txData = { hash: txHash, recipient, amount, timestamp: new Date(), status: 'pending' }; pendingTransactions.set(txHash, txData); updatePendingTransactionsUI(); } function removePendingTransaction(txHash) { pendingTransactions.delete(txHash); updatePendingTransactionsUI(); } function addDecryptedResult(txHash, transferInfo) { const result = { hash: txHash, recipient: transferInfo.to, amount: ethers.formatEther(transferInfo.amount), timestamp: new Date(), status: 'completed' }; completedTransactions.unshift(result); updateCompletedTransactionsUI(); } function updatePendingTransactionsUI() { const container = document.getElementById('pendingTransactions'); if (pendingTransactions.size === 0) { container.innerHTML = '

No pending transactions

'; return; } container.innerHTML = Array.from(pendingTransactions.values()) .map(tx => `
To: ${tx.recipient}
Amount: ${tx.amount} tokens
Hash: ${tx.hash}
Time: ${tx.timestamp.toLocaleString()}
View on Explorer
`).join(''); } function updateCompletedTransactionsUI() { const container = document.getElementById('completedTransactions'); if (completedTransactions.length === 0) { container.innerHTML = '

No completed transfers

'; return; } container.innerHTML = completedTransactions .map(tx => `
To: ${tx.recipient}
Amount: ${tx.amount} tokens
Hash: ${tx.hash}
Time: ${tx.timestamp.toLocaleString()}
View on Explorer
`).join(''); } async function loadBalance() { try { if (!currentAccount) return; const contract = new ethers.Contract( TRANSFER_CONTRACT_ADDRESS, TRANSFER_ABI, provider ); const balance = await contract.balanceOf(currentAccount); const formattedBalance = ethers.formatEther(balance); document.getElementById('walletBalance').textContent = parseFloat(formattedBalance).toFixed(4); } catch (error) { console.error('Failed to load balance:', error); } } function handleTransferSubmit(event) { event.preventDefault(); const recipient = document.getElementById('recipient').value; const amount = document.getElementById('amount').value; // Validate inputs if (!recipient || !amount) { alert('Please fill in all fields'); return; } if (!recipient.match(/^0x[a-fA-F0-9]{40}$/)) { alert('Invalid recipient address'); return; } sendEncryptedTransfer(recipient, amount); // Reset form document.getElementById('transferFormElement').reset(); } // Initialize on page load document.addEventListener('DOMContentLoaded', () => { // Check if wallet is already connected if (typeof window.ethereum !== 'undefined') { window.ethereum.request({ method: 'eth_accounts' }) .then(accounts => { if (accounts.length > 0) { connectWallet(); } }); } }); ``` ## Step 5: Testing and Running ### Run the Application ```bash theme={null} # Start development server npm run dev # Open browser to http://localhost:5173 ``` ### Test the Flow 1. **Connect Wallet**: Click "Connect Wallet" and approve in MetaMask 2. **Enter Transfer Details**: Input recipient address and amount 3. **Send Transfer**: Click "Encrypt & Send Transfer" 4. **Monitor**: Watch the transaction go through stages: * **Pending**: Transaction submitted and encrypted * **Finality**: Transaction confirmed by consensus * **Decryption**: Original data revealed and executed 5. **View Results**: Check completed transfers section Make sure your MetaMask is connected to the correct BITE-enabled SKALE network with sufficient sFUEL for transaction fees. ## Step 6: Advanced Features ### Committee Rotation Monitoring Add this to monitor committee changes: ```javascript theme={null} async function monitorCommitteeRotation() { try { const committees = await bite.getCommitteesInfo(); if (committees.length === 2) { console.warn('⚠️ Committee rotation in progress'); showRotationWarning(); } // Schedule next check setTimeout(monitorCommitteeRotation, 30000); } catch (error) { console.error('Committee monitoring failed:', error); } } function showRotationWarning() { const warning = document.createElement('div'); warning.className = 'rotation-warning'; warning.innerHTML = '⚠️ Committee rotation in progress - dual encryption active'; warning.style.cssText = ` background: #fff3cd; border: 1px solid #ffeaa7; color: #856404; padding: 12px; border-radius: 8px; margin-bottom: 20px; text-align: center; `; document.querySelector('.container').prepend(warning); // Remove warning after 5 minutes setTimeout(() => warning.remove(), 300000); } ``` ### Real-Time Pending Transaction Updates ```javascript theme={null} async function fetchPendingTransactions() { try { const response = await fetch(SCHAIN_ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_pendingTransactions', params: [], id: 1, }), }); const data = await response.json(); const pendingTxs = data.result || []; // Update UI with new pending transactions pendingTxs.forEach(tx => { if (tx.to === '0x0000000000000000000000000000000000000001') { // BITE magic address addPendingTransaction(tx.hash, 'Encrypted', 'Unknown until finality'); } }); } catch (error) { console.error('Failed to fetch pending transactions:', error); } } // Start polling for pending transactions setInterval(fetchPendingTransactions, 5000); monitorCommitteeRotation(); ``` ## Security Best Practices ### Input Validation ```javascript theme={null} function validateTransferData(recipient, amount) { // Address validation if (!ethers.isAddress(recipient)) { throw new Error('Invalid recipient address'); } // Amount validation const parsedAmount = ethers.parseEther(amount); if (parsedAmount <= 0) { throw new Error('Amount must be greater than 0'); } // Balance check const currentBalance = ethers.parseEther( document.getElementById('walletBalance').textContent ); if (parsedAmount > currentBalance) { throw new Error('Insufficient balance'); } return true; } ``` ### Error Handling ```javascript theme={null} class BiteTransferError extends Error { constructor(message, code) { super(message); this.name = 'BiteTransferError'; this.code = code; } } async function sendEncryptedTransferWithRetry(recipient, amount, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await sendEncryptedTransfer(recipient, amount); } catch (error) { if (attempt === maxRetries) { throw new BiteTransferError( `Failed after ${maxRetries} attempts: ${error.message}`, 'MAX_RETRIES_EXCEEDED' ); } // Exponential backoff await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000) ); } } } ``` ## Troubleshooting ### Common Issues #### "Transaction not decrypting" **Solutions:** 1. Wait longer for consensus finality 2. Check if committee rotation is in progress 3. Verify transaction was sent via BITE protocol #### "Gas limit too low" **Solutions:** 1. Increase gas limit to 300,000 or higher 2. Always set gasLimit manually for BITE transactions #### "Connection failed" **Solutions:** 1. Verify SKALE chain endpoint URL is correct 2. Check network connectivity 3. Ensure chain supports BITE protocol #### "Balance not updating" **Solutions:** 1. Wait for transaction decryption (3-5 seconds after finality) 2. Refresh wallet connection 3. Check if transfer actually succeeded ### Debug Mode Add this to `main.js` for debugging: ```javascript theme={null} const DEBUG = true; // Set to false in production function debug(message, data = null) { if (DEBUG) { console.log(`[BITE DEBUG] ${message}`, data); } } // Usage examples debug('Initializing BITE with endpoint:', SCHAIN_ENDPOINT); debug('Transaction parameters:', txParams); debug('Encrypted transaction:', encryptedParams); debug('Decrypted data:', decryptedData); ``` ## Next Steps Congratulations! You've built a complete encrypted transaction system with BITE. Here are ways to enhance it: ### Additional Features * **Batch transfers**: Encrypt multiple transfers in one transaction * **Recurring payments**: Scheduled encrypted transfers * **Multi-signature support**: Require multiple approvals * **Transaction history**: Persistent storage with encryption keys * **Advanced privacy**: Zero-knowledge proof integration ### Production Considerations * **Error logging**: Comprehensive error tracking * **Performance optimization**: Debouncing and caching * **Security audits**: Professional security review * **User testing**: Comprehensive UX testing * **Documentation**: API documentation for integration ### Integration Opportunities * **DeFi protocols**: Private swaps and lending * **NFT marketplaces**: Confidential bidding * **Gaming platforms**: Private in-game transactions * **Enterprise applications**: Secure supply chain tracking ## Resources * **BITE TypeScript SDK**: [GitHub Repository](https://github.com/skalenetwork/bite-ts) * **API Documentation**: [BITE API & FAQs](/developers/bite-protocol/bite-api-and-faqs) * **TypeScript SDK Guide**: [TypeScript SDK](/developers/bite-protocol/typescript-sdk) * **BITE Protocol**: [Introduction](/concepts/bite-protocol/intro-bite-protocol) * **MEV Protection**: [MEV Deep Dive](/concepts/bite-protocol/mev-deep-dive) ## Related Topics * [BITE Protocol Introduction](/concepts/bite-protocol/intro-bite-protocol) * [TypeScript SDK](/developers/bite-protocol/typescript-sdk) * [BITE API & FAQs](/developers/bite-protocol/bite-api-and-faqs) * [Threshold Schemes](/concepts/threshold-schemes) * [DKG with BLS](/concepts/dkg-with-bls) # Deploy an ERC-1155 Token Source: https://docs.skale.space/cookbook/smart-contracts/deploy-erc1155-token Step-by-step guide to deploying an ERC-1155 (Multi-Token Standard) on SKALE using Foundry or Hardhat with OpenZeppelin Contracts ## Deploy an ERC-1155 Token OpenZeppelin provides secure, community-vetted smart contract templates that follow best practices for ERC-1155 multi-token contracts. ERC-1155 enables you to deploy **multiple fungible and non-fungible tokens** from a single contract — ideal for gaming, collectibles, and multi-asset systems. ## Step 0: Setup Foundry If your Foundry project isn't set up yet, please go to the [Foundry setup section](../deployment/setup-foundry) before proceeding. ## Step 1: Create the ERC-1155 Contract Create a contract script in `src/MyERC721.sol`. Run: ```shell theme={null} touch src/MyERC1155.sol ``` Add the contract code: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract MyERC1155 is ERC1155 { // Token IDs uint256 public constant GOLD = 0; uint256 public constant SILVER = 1; uint256 public constant SWORD = 2; uint256 public constant SHIELD = 3; constructor() ERC1155("https://myapi.com/api/token/{id}.json") { // Initial supply _mint(msg.sender, GOLD, 1000, ""); _mint(msg.sender, SILVER, 500, ""); _mint(msg.sender, SWORD, 1, ""); _mint(msg.sender, SHIELD, 1, ""); } function mint(address to, uint256 id, uint256 amount) public { _mint(to, id, amount, ""); } function mintBatch( address to, uint256[] memory ids, uint256[] memory amounts ) public { _mintBatch(to, ids, amounts, ""); } } ``` This contract: * Inherits OpenZeppelin’s `ERC1155` * Supports multiple token types * Sets a metadata URI using `{id}` placeholders * Implements single & batch minting * Mints initial supply on deployment The URI string uses `{id}` as a placeholder that will be replaced automatically by compatible metadata servers. ## Step 2: Compile the Contract ```shell theme={null} forge build ``` ## Step 3: Prepare Signer for Deployment Create a Foundry keystore: ```shell theme={null} cast wallet import skale-deployer --private-key $(cast wallet new | grep 'Private key:' | awk '{print $3}') ``` Be sure to store your keystore password securely — it cannot be recovered. Retrieve your wallet address: ```shell theme={null} cast wallet address --account skale-deployer ``` Visit the [sFUEL Station](https://www.sfuelstation.com/), toggle testnet, and fill up your SKALE deployer wallet. Always toggle testnet first when using testnet chains! ## Step 4: Deploy the Contract ```shell theme={null} forge create src/MyERC1155.sol:MyERC1155 \ --account skale-deployer \ --rpc-url skale_testnet \ --broadcast \ --legacy ``` The `--legacy` flag is required for SKALE deployments. Example output: ```shell theme={null} Enter keystore password: Deployer: 0x63a38D694de837dDF765f9b2704814275586D812 Deployed to: 0x4A435f6E471f773173774E860EBDcd17B132a2b4 Transaction hash: 0x3f6cc66e860cb82a6a62e3f5181c401e5558d60d622a9157437002e16c1ce488 ``` ## Step 5: Verify Your Smart Contract ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyERC1155.sol:MyERC1155 \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` ## Step 0: Setup Hardhat If your Hardhat project isn't set up yet, go to the [Hardhat setup section](../deployment/setup-hardhat) before proceeding. ## Step 1: Create the ERC-1155 Contract Create a contract script in `contracts/MyERC721.sol`. Run: ```shell theme={null} touch contracts/MyERC1155.sol ``` Add the contract code: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; contract MyERC1155 is ERC1155 { constructor() ERC1155("https://myapi.com/api/token/{id}.json") {} function mint(address to, uint256 id, uint256 amount) public { _mint(to, id, amount, ""); } function mintBatch( address to, uint256[] memory ids, uint256[] memory amounts ) public { _mintBatch(to, ids, amounts, ""); } } ``` ## Step 2: Compile Contracts ```shell theme={null} npx hardhat compile ``` ## Step 3: Create a Deployment Script Create a deployment script in `scripts/deploy_erc1155.sol`. Run: ```shell theme={null} touch scripts/deploy_erc1155 ``` Add the deployment code: ```javascript theme={null} const hre = require("hardhat"); async function main() { const MultiToken = await hre.ethers.getContractFactory("MyERC1155"); const multi = await MultiToken.deploy(); await multi.waitForDeployment(); console.log("Contract deployed to:", await multi.getAddress()); } main().catch((error) => { console.error(error); process.exit(1); }); ``` ## Step 4: Run Tests ```shell theme={null} npx hardhat test ``` ## Step 5: Deploy to SKALE ```shell theme={null} npx hardhat run scripts/deploy_erc1155.js --network skale_testnet ``` ## SKALE-Specific Considerations 1. **Transaction Types**\ Hardhat usually handles this automatically, but ensure your version supports legacy transactions if required. 2. **Gas Configuration** ```javascript theme={null} networks: { skale_testnet: { url: "...", accounts: [...], gasPrice: 0, // SKALE gas is always zero }, }, ``` 3. **Contract Verification** ```shell theme={null} npx hardhat verify --network skale_testnet ``` *** ## Next Steps Congratulations! You've successfully deployed an ERC-1155 multi-token contract on SKALE. You can now: * Mint fungible and non-fungible tokens * Batch mint or transfer multiple token types * Build advanced games, inventories, or multi-asset dApps * Use metadata per token ID ## Related Topics * [Solidity Quickstart](/developers/solidity-quickstart) * [Deploy on SKALE](/developers/deploy-on-skale) * [Deploy an ERC-20 Token](/cookbook/smart-contracts/deploy-erc20-token) * [Deploy an ERC-721 Token](/cookbook/smart-contracts/deploy-erc721-token) * [Troubleshooting](/developers/troubleshooting) # Deploy an ERC-20 Token Source: https://docs.skale.space/cookbook/smart-contracts/deploy-erc20-token Step-by-step guide to deploying an ERC-20 token on SKALE using OpenZeppelin Contracts ## Deploy an ERC-20 Token OpenZeppelin provides secure, community-vetted smart contract templates that follow best practices. ## Step 0: Setup Foundry If your foundry project isn't setup yet please go to [foundry setup section](../deployment/setup-foundry) before proceeding. ## Step 1: Create the ERC-20 Contract Create a contract script in `src/MyERC20.sol`. Run: ```shell theme={null} touch src/MyERC20.sol ``` Add the code: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyERC20 is ERC20 { constructor() ERC20("MyToken", "MTK") { // Mint 1 million tokens to the deployer (18 decimals) _mint(msg.sender, 1000000 * 10 ** decimals()); } } ``` This contract: * Inherits from OpenZeppelin's `ERC20` contract * Sets the token name to "MyToken" and symbol to "MTK" * Mints 1,000,000 tokens to the deployer address upon deployment ## Step 2: Compile the Contract Compile your contract: ```shell theme={null} forge build ``` ## Step 3: Prepare Signer for Deployment This tutorial uses the Foundry Keystore for increased security. Create a new keystore: ```shell theme={null} cast wallet import skale-deployer --private-key $(cast wallet new | grep 'Private key:' | awk '{print $3}') ``` Provide a password to encrypt the keystore file when running the above command. If you forget this password, you will not be able to recover it. Get your wallet address: ```shell theme={null} cast wallet address --account skale-deployer ``` Copy the address and head over to the [sFUEL Station](https://www.sfuelstation.com/). Input the address, toggle testnet, and fill up on your SKALE Chain. Make sure you toggle testnet on first if you're using a testnet chain! ## Step 4: Deploy the Contract Deploy your ERC-20 token to SKALE: ```shell theme={null} forge create src/MyERC20.sol:MyERC20 \ --account skale-deployer \ --rpc-url skale_testnet \ --broadcast \ --legacy ``` The `--legacy` flag is required for SKALE Chains. For more information, see [Troubleshooting](/developers/troubleshooting). A successful deployment should look something like this: ```shell theme={null} Enter keystore password: Deployer: 0x63a38D694de837dDF765f9b2704814275586D812 Deployed to: 0x4A435f6E471f773173774E860EBDcd17B132a2b4 Transaction hash: 0x3f6cc66e860cb82a6a62e3f5181c401e5558d60d622a9157437002e16c1ce488 ``` ## Step 5: Verify Your Smart Contract Verify your smart contract on the block explorer: ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyERC20.sol:MyERC20 \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` Replace `` with your deployed contract address. ## Step 0: Setup Hardhat If your hardhat project isn't setup yet please go to [hardhat setup section](../deployment/setup-hardhat) before proceeding. ## Step 1: Create the ERC-20 Contract Create a contract script in `contracts/MyERC20.sol`. Run: ```shell theme={null} touch contracts/MyERC20.sol ``` Add the code: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyERC20 is ERC20 { constructor() ERC20("MyToken", "MTK") { // Mint 1 million tokens to the deployer (18 decimals) _mint(msg.sender, 1000000 * 10 ** decimals()); } } ``` This contract: * Inherits from OpenZeppelin's `ERC20` contract * Sets the token name to "MyToken" and symbol to "MTK" * Mints 1,000,000 tokens to the deployer address upon deployment ## Step 2: Compile Contracts Compile your contracts: ```shell theme={null} npx hardhat compile ``` ## Step 3: Create a Deployment Script Create a deployment script in `scripts/deploy_erc20.js`. Run: ```shell theme={null} touch scripts/deploy_erc20.js ``` Add the deployment code: ```javascript theme={null} const hre = require("hardhat"); async function main() { const MyContract = await hre.ethers.getContractFactory("MyERC20"); const myContract = await MyContract.deploy(); await myContract.waitForDeployment(); console.log("Contract deployed to:", await myContract.getAddress()); } main() .then(() => process.exit(0)) .catch((error) => { console.error(error); process.exit(1); }); ``` ## Step 4: Run Tests Run your tests: ```shell theme={null} npx hardhat test ``` ## Step 5: Deploy to SKALE Deploy your contract to SKALE: ```shell theme={null} npx hardhat run scripts/deploy_erc20.js --network skale_testnet ``` ## SKALE-Specific Considerations When deploying to SKALE with Hardhat: 1. **Transaction Type**: Hardhat automatically handles transaction types, but ensure your Hardhat version supports legacy transactions if needed 2. **Gas Configuration**: SKALE has zero gas fees, but you still need sFUEL for transactions: ```javascript theme={null} networks: { skale_testnet: { url: "...", accounts: [...], gasPrice: 0, // SKALE has zero gas fees }, }, ``` 3. **Contract Verification**: Verify contracts using Hardhat's verify plugin: ```shell theme={null} npx hardhat verify --network skale_testnet ``` ## Next Steps Congratulations! You've successfully deployed an ERC-20 token on SKALE. You can now: * Transfer tokens to other addresses * Approve spending allowances * Integrate the token into your dApp ## Related Topics * [Solidity Quickstart](/developers/solidity-quickstart) * [Deploy on SKALE](/developers/deploy-on-skale) * [Payments on SKALE](/developers/payments-on-skale) * [Troubleshooting](/developers/troubleshooting) # Deploy an ERC-721 Token Source: https://docs.skale.space/cookbook/smart-contracts/deploy-erc721-token Step-by-step guide to deploying an ERC-721 NFT contract on SKALE using Foundry or Hardhat with OpenZeppelin Contracts ## Deploy an ERC-721 Token OpenZeppelin provides secure, community-vetted smart contract templates that follow best practices for ERC-721 (NFT) development. ## Step 0: Setup Foundry If your Foundry project isn't set up yet, please go to the [Foundry setup section](../deployment/setup-foundry) before proceeding. ## Step 1: Create the ERC-721 Contract Create a contract script in `src/MyERC721.sol`. Run: ```shell theme={null} touch src/MyERC721.sol ``` Add the code: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MyERC721 is ERC721 { uint256 private _nextTokenId; constructor() ERC721("MyNFT", "MNFT") {} function mint(address to) public { uint256 tokenId = _nextTokenId; _safeMint(to, tokenId); _nextTokenId++; } function getNextTokenId() public view returns (uint256) { return _nextTokenId; } } ``` This contract: * Inherits from OpenZeppelin’s `ERC721` contract * Sets the name to `"MyNFT"` and symbol `"MNFT"` * Includes a `mint` function allowing the creation of NFTs * Tracks the next available token ID ## Step 2: Compile the Contract Compile your contract: ```shell theme={null} forge build ``` ## Step 3: Prepare Signer for Deployment This tutorial uses the Foundry Keystore for increased security. Create a new keystore: ```shell theme={null} cast wallet import skale-deployer --private-key $(cast wallet new | grep 'Private key:' | awk '{print $3}') ``` Provide a password to encrypt the keystore file when running the command. If you forget the password, it cannot be recovered. Get your wallet address: ```shell theme={null} cast wallet address --account skale-deployer ``` Visit the [sFUEL Station](https://www.sfuelstation.com/), toggle testnet, and fill up on sFUEL. Make sure testnet mode is enabled on the sFUEL Station if you're using a testnet SKALE Chain! ## Step 4: Deploy the Contract Deploy your ERC-721 contract: ```shell theme={null} forge create src/MyERC721.sol:MyERC721 \ --account skale-deployer \ --rpc-url skale_testnet \ --broadcast \ --legacy ``` The `--legacy` flag is required for SKALE Chains. For more information, see [Troubleshooting](/developers/troubleshooting). Example deployment output: ```shell theme={null} Enter keystore password: Deployer: 0x63a38D694de837dDF765f9b2704814275586D812 Deployed to: 0x4A435f6E471f773173774E860EBDcd17B132a2b4 Transaction hash: 0x3f6cc66e860cb82a6a62e3f5181c401e5558d60d622a9157437002e16c1ce488 ``` ## Step 5: Verify Your Smart Contract Verify the contract on the block explorer: ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyERC721.sol:MyERC721 \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` Replace `` with your smart contract address. ## Step 0: Setup Hardhat If your Hardhat project isn't set up yet, please go to the [Hardhat setup section](../deployment/setup-hardhat) before proceeding. ## Step 1: Create the ERC-721 Contract Create a contract script in `contracts/MyERC721.sol`. Run: ```shell theme={null} touch contracts/MyERC721.sol ``` Add the code: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MyERC721 is ERC721 { uint256 private _nextTokenId; constructor() ERC721("MyNFT", "MNFT") {} function mint(address to) public { uint256 tokenId = _nextTokenId; _safeMint(to, tokenId); _nextTokenId++; } } ``` ## Step 2: Compile Contracts ```shell theme={null} npx hardhat compile ``` ## Step 3: Create a Deployment Script Create the deployemnt script in `scripts/deploy_erc721.js`. Run: ```shell theme={null} touch scripts/deploy_erc721.js ``` Add the deployment code: ```javascript theme={null} const hre = require("hardhat"); async function main() { const NFT = await hre.ethers.getContractFactory("MyERC721"); const nft = await NFT.deploy(); await nft.waitForDeployment(); console.log("Contract deployed to:", await nft.getAddress()); } main().catch((error) => { console.error(error); process.exit(1); }); ``` ## Step 4: Run Tests ```shell theme={null} npx hardhat test ``` ## Step 5: Deploy to SKALE ```shell theme={null} npx hardhat run scripts/deploy_erc721.js --network skale_testnet ``` ## SKALE-Specific Considerations 1. **Transaction Type**: Hardhat generally handles transaction types automatically, but confirm your version supports legacy transactions if needed. 2. **Gas Configuration**:\ SKALE has zero gas fees, but still requires sFUEL for transactions. ```javascript theme={null} networks: { skale_testnet: { url: "...", accounts: [...], gasPrice: 0, // SKALE has zero gas fees }, }, ``` 3. **Contract Verification**: ```shell theme={null} npx hardhat verify --network skale_testnet ``` *** ## Next Steps Congratulations! You've successfully deployed an ERC-721 token on SKALE. You can now: * Mint unique NFTs * Transfer NFTs between addresses * Approve operators * Build NFT marketplaces or dApps on SKALE ## Related Topics * [Solidity Quickstart](/developers/solidity-quickstart) * [Deploy on SKALE](/developers/deploy-on-skale) * [Payments on SKALE](/developers/payments-on-skale) * [Deploy an ERC-20 Token](/cookbook/smart-contracts/deploy-erc20-token) * [Troubleshooting](/developers/troubleshooting) # Verify Smart Contracts Source: https://docs.skale.space/cookbook/smart-contracts/verify-smart-contracts Guide to verifying ERC-20, ERC-721, and ERC-1155 contracts on SKALE block explorers ## Verify Smart Contracts After deploying your smart contracts to SKALE, it's important to verify them on the block explorer. Verification makes your contract source code publicly available, allowing users to interact with your contract with confidence and enabling better transparency. ## Prerequisites * A deployed smart contract on SKALE * The source code and constructor arguments used for deployment * Foundry or Hardhat installed (depending on your deployment method) ## Verification Methods Foundry provides a built-in verification command that works with Blockscout (the explorer used by SKALE). #### Basic Verification ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyContract.sol:MyContract \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` #### Verification with Constructor Arguments If your contract has constructor arguments: ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyContract.sol:MyContract \ --constructor-args $(cast abi-encode "constructor(string,uint256)" "MyToken" 1000000) \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` #### Verification with Libraries If your contract uses libraries (like OpenZeppelin): ```shell theme={null} forge verify-contract \ --rpc-url skale_testnet \ \ src/MyERC20.sol:MyERC20 \ --libraries @openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20: \ --verifier blockscout \ --verifier-url https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api ``` Hardhat also supports contract verification through the `@nomicfoundation/hardhat-verify` plugin. #### Configuration Ensure your `hardhat.config.js` includes the verify plugin configuration: ```javascript theme={null} require("@nomicfoundation/hardhat-verify"); module.exports = { // ... other config etherscan: { apiKey: { skale_testnet: "your-api-key", // Not required for Blockscout }, customChains: [ { network: "skale_testnet", chainId: 1444673419, urls: { apiURL: "https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/api", browserURL: "https://juicy-low-small-testnet.explorer.testnet.skalenodes.com", }, }, ], }, }; ``` #### Basic Verification ```shell theme={null} npx hardhat verify --network skale_testnet ``` #### Verification with Constructor Arguments ```shell theme={null} npx hardhat verify --network skale_testnet "arg1" "arg2" ``` ## Finding Your Block Explorer URL Each SKALE Chain has its own block explorer. To find your chain's explorer URL: 1. Visit the [SKALE Portal](https://portal.skale.space) 2. Select your chain 3. Find the "Explorer" link or API endpoint The API URL format is typically: * Testnet: `https://.explorer.testnet.skalenodes.com/api` * Mainnet: `https://.explorer.mainnet.skalenodes.com/api` ## Troubleshooting ### Contract Already Verified If you see "Contract is already verified", the contract has been successfully verified previously. ### Verification Failed Common issues and solutions: 1. **Wrong Constructor Arguments**: Double-check your constructor arguments 2. **Compiler Version Mismatch**: Ensure you're using the same Solidity version 3. **Optimization Settings**: Match your compiler optimization settings 4. **Library Addresses**: Verify all library addresses are correct ### Manual Verification If automated verification fails, you can manually verify: 1. Go to your contract's page on the block explorer 2. Click "Verify and Publish" 3. Fill in the contract details manually 4. Paste your source code ## Best Practices 1. **Verify Immediately**: Verify contracts right after deployment 2. **Save Constructor Args**: Keep a record of constructor arguments 3. **Use Version Control**: Commit your source code for reference 4. **Test Verification**: Test verification on testnet before mainnet ## Related Topics * [Deploy an ERC-20 Token](/cookbook/smart-contracts/deploy-erc20-token) * [Deploy an ERC-721 Token](/cookbook/smart-contracts/deploy-erc721-token) * [Deploy an ERC-1155 Token](/cookbook/smart-contracts/deploy-erc1155-token) * [Setup Foundry](/cookbook/deployment/setup-foundry) * [Setup Hardhat](/cookbook/deployment/setup-hardhat) # Accept Payments Source: https://docs.skale.space/cookbook/x402/accepting-payments Protect HTTP endpoints with x402 payments using middleware Protect your HTTP endpoints with x402 payments using middleware. The middleware enforces the x402 handshake, forwards payment requirements to a facilitator, inspects payment headers from clients, and handles settlement before allowing requests to proceed. ## Prerequisites * Node.js and npm installed * A SKALE Chain endpoint * Understanding of x402 protocol * A facilitator service (see [Run a Facilitator](/cookbook/x402/facilitator)) ## Overview When a client reaches a paywalled endpoint, the middleware: 1. Returns a `402 Payment Required` response with payment requirements if the incoming request lacks a valid payment header 2. When a payment header is present, forwards it to the facilitator's `/verify` and `/settle` endpoints 3. If settlement succeeds, the request continues; otherwise another 402 is returned *** ## Environment Setup Create a `.env` file with the following variables: ```bash theme={null} # Facilitator URL FACILITATOR_URL=https://facilitator.dirtroad.dev # Your wallet address to receive payments FACILITATOR_RECEIVING_ADDRESS=0xfacilitator_receiving_address # USDC token contract on SKALE PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)" # SKALE Chain ID NETWORK_CHAIN_ID=324705682 # Server port PORT=3000 ``` *** ## Implementation ### Step 1: Install Dependencies ```bash theme={null} npm install hono @hono/node-server @x402/hono @x402/evm @x402/core dotenv ``` ### Step 2: Create the Server ```typescript theme={null} import { Hono } from "hono"; import { serve } from "@hono/node-server"; import { paymentMiddleware, x402ResourceServer } from "@x402/hono"; import { ExactEvmScheme } from "@x402/evm/exact/server"; import { HTTPFacilitatorClient } from "@x402/core/server"; import type { Network } from "@x402/core/types"; import "dotenv/config"; const app = new Hono(); async function main() { const facilitatorUrl = process.env.FACILITATOR_URL!; const receivingAddress = process.env.RECEIVING_ADDRESS as `0x${string}`; const paymentTokenAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`; const paymentTokenName = process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)"; const networkChainId = process.env.NETWORK_CHAIN_ID || "324705682"; const network: Network = `eip155:${networkChainId}`; // Setup facilitator client and resource server const facilitatorClient = new HTTPFacilitatorClient({ url: facilitatorUrl }); const resourceServer = new x402ResourceServer(facilitatorClient); // Register the exact scheme for EVM networks resourceServer.register("eip155:*", new ExactEvmScheme()); // Public endpoint app.get("/", (c) => { return c.json({ message: "Welcome! Access /premium/* endpoints for paid content" }); }); // Apply payment middleware to protected routes app.use( paymentMiddleware( { "GET /premium/data": { accepts: [ { scheme: "exact", network: network, payTo: receivingAddress, price: { amount: "10000", asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1", }, }, }, ], description: "Premium data access", mimeType: "application/json", }, }, resourceServer, ), ); // Protected endpoint app.get("/premium/data", (c) => { return c.json({ secret: "Premium data unlocked!", timestamp: new Date().toISOString(), }); }); const port = Number(process.env.PORT) || 3000; serve({ fetch: app.fetch, port }, () => { console.log(`Server running on http://localhost:${port}`); }); } main().catch(console.error); ``` ### Step 3: Run the Server ```bash theme={null} npx tsx server.ts ``` ### Multiple Protected Routes Add multiple routes with different pricing: ```typescript theme={null} app.use( paymentMiddleware( { "GET /premium/data": { accepts: [ { scheme: "exact", network: network, payTo: receivingAddress, price: { amount: "10000", asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1" }, }, }, ], description: "Basic premium data", mimeType: "application/json", }, "GET /premium/advanced": { accepts: [ { scheme: "exact", network: network, payTo: receivingAddress, price: { amount: "50000", asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1" }, }, }, ], description: "Advanced premium data", mimeType: "application/json", }, }, resourceServer, ), ); ``` ### Step 1: Install Dependencies ```bash theme={null} npm install hono @hono/node-server @faremeter/middleware dotenv ``` ### Step 2: Create the Server ```typescript theme={null} import { Hono } from "hono"; import { serve } from "@hono/node-server"; import { createMiddleware } from "@faremeter/middleware/hono"; import "dotenv/config"; const app = new Hono(); async function main() { const facilitatorUrl = process.env.FACILITATOR_URL!; const receivingAddress = process.env.RECEIVING_ADDRESS as `0x${string}`; const paymentTokenAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`; const paymentTokenName = process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)"; const networkChainId = process.env.NETWORK_CHAIN_ID || "324705682"; // Create payment middleware const paywalled = await createMiddleware({ facilitatorURL: facilitatorUrl, accepts: [ { scheme: "exact", network: `eip155:${networkChainId}`, amount: "10000", maxTimeoutSeconds: 300, payTo: receivingAddress, asset: paymentTokenAddress, description: "Premium data access", mimeType: "application/json", extra: { name: paymentTokenName, version: "1", }, }, ], }); // Public endpoint app.get("/", (c) => { return c.json({ message: "Welcome! Access /premium/* endpoints for paid content" }); }); // Protected endpoint app.get("/premium/data", paywalled, (c) => { return c.json({ secret: "Premium data unlocked!", timestamp: new Date().toISOString(), }); }); const port = Number(process.env.PORT) || 3000; serve({ fetch: app.fetch, port }, () => { console.log(`Server running on http://localhost:${port}`); }); } main().catch(console.error); ``` ### Step 3: Run the Server ```bash theme={null} npx tsx server.ts ``` ### Applying Middleware to Multiple Routes Use route patterns to protect multiple endpoints: ```typescript theme={null} // Apply to all /premium/* routes app.use("/premium/*", paywalled); app.get("/premium/data", (c) => { return c.json({ type: "data", content: "Premium data" }); }); app.get("/premium/weather", (c) => { return c.json({ type: "weather", forecast: "Sunny" }); }); ``` Or create different middleware instances with different pricing: ```typescript theme={null} const basicPaywall = await createMiddleware({ facilitatorURL: facilitatorUrl, accepts: [{ scheme: "exact", network: `eip155:${networkChainId}`, amount: "10000", maxTimeoutSeconds: 300, payTo: receivingAddress, asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1" }, }], }); const premiumPaywall = await createMiddleware({ facilitatorURL: facilitatorUrl, accepts: [{ scheme: "exact", network: `eip155:${networkChainId}`, amount: "50000", maxTimeoutSeconds: 300, payTo: receivingAddress, asset: paymentTokenAddress, extra: { name: paymentTokenName, version: "1" }, }], }); app.get("/basic/data", basicPaywall, (c) => c.json({ tier: "basic" })); app.get("/premium/data", premiumPaywall, (c) => c.json({ tier: "premium" })); ``` *** ## Testing Your Server Once your server is running, you can test it: ```bash theme={null} # Public endpoint (no payment required) curl http://localhost:3000/ # Protected endpoint (returns 402 Payment Required) curl http://localhost:3000/premium/data ``` The protected endpoint will return a `402 Payment Required` response with payment requirements that a client can use to make a payment. ## Error Handling * Invalid facilitator responses throw an exception (HTTP 500) * When settlement fails, the middleware returns a 402 status with error details * Missing configuration returns a 503 Service Unavailable ## Next Steps * [Make Payments (Buyer)](/cookbook/x402/buying) * [Run a Facilitator](/cookbook/x402/facilitator) * [Multi-Token Payments](/cookbook/x402/non-usdc-tokens) ## Resources * [x402 Examples Repository](https://github.com/TheGreatAxios/x402-examples) * [Faremeter Middleware Documentation](https://docs.corbits.dev/api/reference/middleware/overview) * [Coinbase x402 SDK](https://github.com/coinbase/x402) # Make Payments Source: https://docs.skale.space/cookbook/x402/buying Make x402 payments to access paywalled resources This guide shows how to make x402 payments from the buyer's perspective. Use the SDK to handle payment flows, interact with paywalled resources, and process 402 Payment Required responses. ## Prerequisites * Node.js and npm installed * A SKALE Chain endpoint * Understanding of x402 protocol * Familiarity with TypeScript * A wallet with funds (USDC or supported token) ## Overview When accessing a paywalled resource, the flow is: 1. Make a request to the protected endpoint 2. Receive a `402 Payment Required` response with payment requirements 3. Create and sign a payment authorization 4. Retry the request with the payment signature header *** ## Environment Setup Create a `.env` file with the following variables: ```bash theme={null} # Your wallet private key (never commit this!) PRIVATE_KEY=0xYourPrivateKey # USDC token contract on SKALE PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)" ``` *** ## Implementation ### Step 1: Install Dependencies ```bash theme={null} npm install @x402/core @x402/evm viem dotenv ``` ### Step 2: Define Your Chain Create a chain configuration for SKALE: ```typescript theme={null} import { defineChain } from "viem"; export const skaleChain = defineChain({ id: 324705682, name: "SKALE Base Sepolia", nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" }, rpcUrls: { default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] }, }, }); ``` ### Step 3: Create the Payment Client ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; import "dotenv/config"; const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); // Create the x402 client with EVM scheme const evmScheme = new ExactEvmScheme(account); const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); ``` ### Step 4: Access Paywalled Resources ```typescript theme={null} async function accessResource(url: string): Promise { // Make initial request const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); // If no payment required, return data if (response.status !== 402) { return response.json(); } // Get payment requirements from response const responseBody = await response.json(); const paymentRequired = httpClient.getPaymentRequiredResponse( (name: string) => response.headers.get(name), responseBody ); // Create payment payload (signs the authorization) const paymentPayload = await httpClient.createPaymentPayload(paymentRequired); // Get payment headers to send with retry request const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload); // Retry request with payment const paidResponse = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", ...paymentHeaders, }, }); if (!paidResponse.ok) { throw new Error(`Payment failed: ${paidResponse.status}`); } return paidResponse.json(); } ``` ### Complete Working Example ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; import { createPublicClient, http, formatEther } from "viem"; import { defineChain } from "viem"; import "dotenv/config"; export const skaleChain = defineChain({ id: 324705682, name: "SKALE Base Sepolia", nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" }, rpcUrls: { default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] }, }, }); async function main() { const privateKey = process.env.PRIVATE_KEY; if (!privateKey) { throw new Error("PRIVATE_KEY environment variable is required"); } // Setup wallet const account = privateKeyToAccount(privateKey as `0x${string}`); console.log(`Wallet address: ${account.address}`); // Setup x402 client const evmScheme = new ExactEvmScheme(account); const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); // Access paywalled resource const url = "http://localhost:3000/premium/data"; try { const response = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); if (response.status === 402) { console.log("Payment required, processing..."); const responseBody = await response.json(); const paymentRequired = httpClient.getPaymentRequiredResponse( (name: string) => response.headers.get(name), responseBody ); const paymentPayload = await httpClient.createPaymentPayload(paymentRequired); const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload); const paidResponse = await fetch(url, { method: "GET", headers: { "Content-Type": "application/json", ...paymentHeaders, }, }); const data = await paidResponse.json(); console.log("Premium data:", data); } else { const data = await response.json(); console.log("Data:", data); } } catch (error) { console.error("Error:", error); } } main(); ``` ### Run the Client ```bash theme={null} npx tsx client.ts ``` ### Step 1: Install Dependencies ```bash theme={null} npm install @faremeter/fetch @faremeter/wallet-evm @faremeter/payment-evm viem dotenv ``` ### Step 2: Define Your Chain Create a chain configuration for SKALE: ```typescript theme={null} import { defineChain } from "viem"; export const skaleChain = defineChain({ id: 324705682, name: "SKALE Base Sepolia", nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" }, rpcUrls: { default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] }, }, }); ``` ### Step 3: Create the Payment-Enabled Fetch The Faremeter SDK wraps the native `fetch` function to automatically handle 402 responses: ```typescript theme={null} import { createLocalWallet } from "@faremeter/wallet-evm"; import { createPaymentHandler } from "@faremeter/payment-evm/exact"; import { wrap as wrapFetch } from "@faremeter/fetch"; import "dotenv/config"; const wallet = await createLocalWallet(skaleChain, process.env.PRIVATE_KEY!); const fetchWithPayment = wrapFetch(fetch, { handlers: [ createPaymentHandler(wallet, { asset: { address: process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`, contractName: process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)", forwarderName: process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)", forwarderVersion: "1", }, }), ], }); ``` ### Step 4: Access Paywalled Resources ```typescript theme={null} // The wrapped fetch automatically handles 402 responses const response = await fetchWithPayment("http://localhost:3000/premium/data", { method: "GET", headers: { "Content-Type": "application/json" }, }); const data = await response.json(); console.log("Premium data:", data); ``` ### Complete Working Example ```typescript theme={null} import { createLocalWallet } from "@faremeter/wallet-evm"; import { createPaymentHandler } from "@faremeter/payment-evm/exact"; import { wrap as wrapFetch } from "@faremeter/fetch"; import { createPublicClient, http, formatEther, defineChain } from "viem"; import "dotenv/config"; export const skaleChain = defineChain({ id: 324705682, name: "SKALE Base Sepolia", nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" }, rpcUrls: { default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] }, }, }); async function main() { const privateKey = process.env.PRIVATE_KEY; if (!privateKey) { throw new Error("PRIVATE_KEY environment variable is required"); } // Setup wallet const wallet = await createLocalWallet(skaleChain, privateKey); console.log(`Wallet address: ${wallet.account.address}`); // Setup payment-enabled fetch const assetAddress = process.env.PAYMENT_TOKEN_ADDRESS as `0x${string}`; const assetName = process.env.PAYMENT_TOKEN_NAME || "Bridged USDC (SKALE Bridge)"; const fetchWithPayment = wrapFetch(fetch, { handlers: [ createPaymentHandler(wallet, { asset: { address: assetAddress, contractName: assetName, forwarderName: assetName, forwarderVersion: "1", }, }), ], }); // Access paywalled resource - payment handled automatically! const url = "http://localhost:3000/premium/data"; try { console.log(`Accessing ${url}...`); const response = await fetchWithPayment(url, { method: "GET", headers: { "Content-Type": "application/json" }, }); if (!response.ok) { throw new Error(`Request failed: ${response.status}`); } const data = await response.json(); console.log("Premium data:", data); } catch (error) { console.error("Error:", error); } } main(); ``` ### Run the Client ```bash theme={null} npx tsx client.ts ``` ### POST Requests with Body The wrapped fetch works the same way for POST requests: ```typescript theme={null} const response = await fetchWithPayment("http://localhost:3000/premium/analyze", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: "analyze this data" }), }); const result = await response.json(); console.log("Analysis result:", result); ``` *** ## Testing Your Client Make sure you have a server running (see [Accept Payments](/cookbook/x402/accepting-payments)), then run your client: ```bash theme={null} npx tsx client.ts ``` The client will: 1. Make a request to the protected endpoint 2. Receive the 402 response with payment requirements 3. Sign a payment authorization using your wallet 4. Retry the request with the payment header 5. Receive the premium content ## Best Practices 1. **Use Environment Variables**: Never hardcode private keys 2. **Handle Errors Gracefully**: Always wrap payment logic in try-catch 3. **Check Balances**: Ensure wallet has sufficient funds before making payments 4. **Log Transactions**: Keep track of payments for debugging and accounting ## Security Considerations * Never expose private keys in client-side code * Use secure key management (environment variables, secrets manager) * Validate payment amounts before approving * Consider implementing spending limits ## Next Steps * [Accept Payments (Seller)](/cookbook/x402/accepting-payments) * [Run a Facilitator](/cookbook/x402/facilitator) * [Multi-Token Payments](/cookbook/x402/non-usdc-tokens) ## Resources * [Coinbase x402 SDK](https://github.com/coinbase/x402) * [Faremeter SDK Documentation](https://docs.corbits.dev/api/reference/fetch/overview) * [x402 Examples Repository](https://github.com/TheGreatAxios/x402-examples) # Run a Facilitator Source: https://docs.skale.space/cookbook/x402/facilitator Set up an x402 facilitator service for payment processing on SKALE A facilitator in the x402 protocol is a service that processes payments and provides settlement. It exposes `/verify` and `/settle` endpoints that work with x402 middleware to handle payment flows using ERC-3009 `TransferWithAuthorization`. ## Prerequisites * Node.js 18+ * pnpm (install via [pnpm.io/installation](https://pnpm.io/installation)) * A SKALE Chain endpoint * A wallet private key for signing settlement transactions * Basic knowledge of TypeScript ## Overview A facilitator service: 1. Exposes `/verify` endpoint - Validates payment authorizations without on-chain settlement 2. Exposes `/settle` endpoint - Executes on-chain payment settlements 3. Exposes `/supported` endpoint - Returns supported payment schemes and networks 4. Handles EIP-712 signature verification 5. Prevents replay attacks via nonce tracking *** ## Implementation ### Step 1: Project Setup Create a new project and install dependencies: ```bash theme={null} mkdir skale-facilitator cd skale-facilitator pnpm init ``` Install the required packages: ```bash theme={null} pnpm add @x402/core @x402/evm dotenv express viem pnpm add -D @types/express @types/node tsx typescript ``` Update your `package.json`: ```json theme={null} { "name": "skale-facilitator", "version": "1.0.0", "type": "module", "scripts": { "start": "tsx index.ts", "dev": "tsx watch index.ts", "build": "tsc", "typecheck": "tsc --noEmit" } } ``` ### Step 2: TypeScript Configuration Create `tsconfig.json`: ```json theme={null} { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ES2022"], "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true, "outDir": "./dist" }, "include": ["*.ts"], "exclude": ["node_modules", "dist"] } ``` ### Step 3: Environment Configuration Create `.env` file in the project root: ```bash theme={null} touch .env ``` Add the variables: ```bash theme={null} FACILITATOR_SIGNER_PK=your_private_key_here PORT=4022 ``` Never commit your private key to version control. Add `.env` to your `.gitignore` file. ### Step 4: Main Application Create `facilitator.ts` with the facilitator server: ```typescript theme={null} import { x402Facilitator } from "@x402/core/facilitator"; import { PaymentPayload, PaymentRequirements, SettleResponse, VerifyResponse, } from "@x402/core/types"; import { toFacilitatorEvmSigner } from "@x402/evm"; import { registerExactEvmScheme } from "@x402/evm/exact/facilitator"; import dotenv from "dotenv"; import express from "express"; import { createWalletClient, http, publicActions } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { skaleBaseSepoliaTestnet } from "viem/chains"; dotenv.config(); const PORT = process.env.PORT || "4022"; if (!process.env.FACILITATOR_SIGNER_PK) { console.error("FACILITATOR_SIGNER_PK environment variable is required"); process.exit(1); } const evmAccount = privateKeyToAccount( process.env.FACILITATOR_SIGNER_PK as `0x${string}` ); console.info(`EVM Facilitator account: ${evmAccount.address}`); const viemClient = createWalletClient({ account: evmAccount, chain: skaleBaseSepoliaTestnet, transport: http(), }).extend(publicActions); const evmSigner = toFacilitatorEvmSigner({ getCode: (args: { address: `0x${string}` }) => viemClient.getCode(args), address: evmAccount.address, readContract: (args: { address: `0x${string}`; abi: readonly unknown[]; functionName: string; args?: readonly unknown[]; }) => viemClient.readContract({ ...args, args: args.args || [], }), verifyTypedData: (args: { address: `0x${string}`; domain: Record; types: Record; primaryType: string; message: Record; signature: `0x${string}`; }) => viemClient.verifyTypedData(args as Parameters[0]), writeContract: (args: { address: `0x${string}`; abi: readonly unknown[]; functionName: string; args: readonly unknown[]; }) => viemClient.writeContract({ ...args, args: args.args || [], }), sendTransaction: (args: { to: `0x${string}`; data: `0x${string}` }) => viemClient.sendTransaction(args), waitForTransactionReceipt: (args: { hash: `0x${string}` }) => viemClient.waitForTransactionReceipt(args), }); const facilitator = new x402Facilitator() .onBeforeVerify(async (context) => { console.log("Before verify", context); }) .onAfterVerify(async (context) => { console.log("After verify", context); }) .onVerifyFailure(async (context) => { console.log("Verify failure", context); }) .onBeforeSettle(async (context) => { console.log("Before settle", context); }) .onAfterSettle(async (context) => { console.log("After settle", context); }) .onSettleFailure(async (context) => { console.log("Settle failure", context); }); registerExactEvmScheme(facilitator, { signer: evmSigner, networks: "eip155:324705682", deployERC4337WithEIP6492: true, }); const app = express(); app.use(express.json()); app.post("/verify", async (req, res) => { try { const { paymentPayload, paymentRequirements } = req.body as { paymentPayload: PaymentPayload; paymentRequirements: PaymentRequirements; }; if (!paymentPayload || !paymentRequirements) { return res.status(400).json({ error: "Missing paymentPayload or paymentRequirements", }); } const response: VerifyResponse = await facilitator.verify( paymentPayload, paymentRequirements ); res.json(response); } catch (error) { console.error("Verify error:", error); res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error", }); } }); app.post("/settle", async (req, res) => { try { const { paymentPayload, paymentRequirements } = req.body; if (!paymentPayload || !paymentRequirements) { return res.status(400).json({ error: "Missing paymentPayload or paymentRequirements", }); } const response: SettleResponse = await facilitator.settle( paymentPayload as PaymentPayload, paymentRequirements as PaymentRequirements ); res.json(response); } catch (error) { console.error("Settle error:", error); if ( error instanceof Error && error.message.includes("Settlement aborted:") ) { return res.json({ success: false, errorReason: error.message.replace("Settlement aborted: ", ""), network: req.body?.paymentPayload?.network || "unknown", } as SettleResponse); } res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error", }); } }); app.get("/supported", async (req, res) => { try { const response = facilitator.getSupported(); res.json(response); } catch (error) { console.error("Supported error:", error); res.status(500).json({ error: error instanceof Error ? error.message : "Unknown error", }); } }); app.listen(parseInt(PORT), () => { console.log(`Facilitator listening on port ${PORT}`); }); ``` *** ## Running the Facilitator Start the development server: ```bash theme={null} pnpm dev ``` Your facilitator will be available at `http://localhost:4022`. *** ## API Endpoints ### GET /supported Returns supported payment schemes and networks. **Response:** ```json theme={null} { "kinds": [ { "x402Version": 2, "scheme": "exact", "network": "eip155:324705682" } ], "extensions": [], "signers": { "eip155": ["0x..."] } } ``` ### POST /verify Validates payment authorization without on-chain settlement. **Request:** ```json theme={null} { "paymentPayload": { "x402Version": 2, "resource": { "url": "http://localhost:4021/weather", "description": "Weather data", "mimeType": "application/json" }, "accepted": { "scheme": "exact", "network": "eip155:324705682", "asset": "0x61a26022927096f444994dA1e53F0FD9487EAfcf", "amount": "1000", "payTo": "0x...", "maxTimeoutSeconds": 300, "extra": { "name": "Axios USD", "version": "1" } }, "payload": { "signature": "0x...", "authorization": {} } }, "paymentRequirements": { "scheme": "exact", "network": "eip155:324705682", "asset": "0x61a26022927096f444994dA1e53F0FD9487EAfcf", "amount": "1000", "payTo": "0x...", "maxTimeoutSeconds": 300, "extra": { "name": "Axios USD", "version": "1" } } } ``` **Response (success):** ```json theme={null} { "isValid": true, "payer": "0x..." } ``` **Response (failure):** ```json theme={null} { "isValid": false, "invalidReason": "invalid_signature" } ``` ### POST /settle Settles a verified payment by broadcasting the transaction on-chain. Request body is identical to `/verify`. **Response (success):** ```json theme={null} { "success": true, "transaction": "0x...", "network": "eip155:324705682", "payer": "0x..." } ``` **Response (failure):** ```json theme={null} { "success": false, "errorReason": "insufficient_balance", "transaction": "", "network": "eip155:324705682" } ``` *** ## Lifecycle Hooks The facilitator supports lifecycle hooks for custom logic: ```typescript theme={null} const facilitator = new x402Facilitator() .onBeforeVerify(async (context) => { // Log or validate before verification }) .onAfterVerify(async (context) => { // Track verified payments }) .onVerifyFailure(async (context) => { // Handle verification failures }) .onBeforeSettle(async (context) => { // Validate before settlement // Return { abort: true, reason: "..." } to cancel }) .onAfterSettle(async (context) => { // Track successful settlements }) .onSettleFailure(async (context) => { // Handle settlement failures }); ``` *** ## Security Considerations * Store private keys securely in environment variables * Never commit `.env` files to version control * Implement rate limiting for production deployments * Monitor for suspicious activity * Use HTTPS in production * Consider using a multi-sig wallet for high-value operations ## Next Steps * [Accept Payments](/cookbook/x402/accepting-payments) * [Make Payments](/cookbook/x402/buying) * [Multi-Token Payments](/cookbook/x402/non-usdc-tokens) ## Resources * [x402 Protocol](https://x402.org) * [ERC-3009 Specification](https://eips.ethereum.org/EIPS/eip-3009) * [Coinbase x402 SDK](https://github.com/coinbase/x402) # BITE API Source: https://docs.skale.space/developers/bite-protocol/bite-api-and-faqs Technical documentation for BITE's JSON-RPC API methods and transaction structure ## BITE API This section provides detailed technical documentation for BITE's APIs, both off-chain (TypeScript SDK) and on-chain (Solidity helpers). ## Off-Chain API (bite-ts) The `@skalenetwork/bite` TypeScript library provides methods for encrypting data before submitting to the blockchain. ### `new BITE(endpoint: string)` Creates a new BITE instance configured with a JSON-RPC endpoint. **Parameters:** * `endpoint: string` – The BITE URL provider (JSON-RPC endpoint) *** ### `bite.encryptTransaction(tx)` Encrypts a transaction using BLS threshold encryption. The encrypted transaction will have its `to` field set to the BITE magic address. **Parameters:** * `tx: object` – Transaction object with `data` and `to` fields as hex strings **Returns:** * `Promise` – Encrypted transaction with modified `data` and `to` fields **Encryption Process:** 1. RLP encodes original `data` and `to` fields 2. Encrypts encoded data using AES with randomly generated key 3. Encrypts AES key using BLS threshold encryption 4. Creates final payload: `[EPOCH_ID, ENCRYPTED_BITE_DATA]` **Committee Behavior:** * **Single Committee:** AES key encrypted with current BLS public key * **Dual Committee:** During rotation, AES key encrypted twice (current + next committee keys) *** ### `BITE.encryptTransactionWithCommitteeInfo(tx, committees)` Static method that encrypts using provided committee info, avoiding internal RPC call. **Parameters:** * `tx: object` – Transaction with `data` and `to` fields * `committees: Array` – Committee info objects (from `getCommitteesInfo`) **Returns:** * `Promise` – Encrypted transaction **Use Case:** Offline/cached encryption when committee info is already known. *** ### `bite.encryptMessage(message)` Encrypts a raw hex-encoded message using BLS threshold encryption. **Parameters:** * `message: string` – Hex string to encrypt (with/without `0x` prefix) **Returns:** * `Promise` – Encrypted hex string in RLP format with epoch and encryption data **Note:** Encrypts raw data directly without transaction formatting. *** ### `bite.getDecryptedTransactionData(transactionHash)` Retrieves decrypted transaction data after consensus finality using `bite_getDecryptedTransactionData` JSON-RPC method. **Parameters:** * `transactionHash: string` – The transaction hash **Returns:** * `Promise` – JSON with `data` and `to` keys containing original decrypted fields *** ### `bite.getCommitteesInfo()` Fetches committee information using `bite_getCommitteesInfo` JSON-RPC method. **Returns:** * `Promise` – Array of 1-2 committee objects: * `commonBLSPublicKey: string` – 256-char hex (128-byte BLS public key) * `epochId: number` – Epoch identifier **Array Contents:** * **1 element:** Normal operation (single active committee) * **2 elements:** Committee rotation period (scheduled for next 3 minutes) **Use Cases:** Rotation monitoring, epoch tracking, key management *** ## On-Chain API (bite-solidity) The `@skalenetwork/bite-solidity` library provides Solidity helpers for creating Conditional Transactions (CTX) that request decryption from within smart contracts. ### Installation ```bash theme={null} # npm npm i @skalenetwork/bite-solidity # forge forge install skalenetwork/bite-solidity echo "@skalenetwork/bite-solidity/=lib/bite-solidity/" >> remappings.txt ``` **Compiler Requirements** * Solidity version: **>= 0.8.27** * EVM version: **istanbul** or lower ### Import ```solidity theme={null} import { BITE } from "@skalenetwork/bite-solidity/BITE.sol"; import { IBiteSupplicant } from "@skalenetwork/bite-solidity/interfaces/IBiteSupplicant.sol"; ``` ### `BITE.submitCTX(address, gasLimit, encryptedArgs, plaintextArgs)` Creates a Conditional Transaction for decryption. **Parameters:** * `address`: CTX handler address (use `BITE.SUBMIT_CTX_ADDRESS`) * `gasLimit`: Gas for callback (in gas units, e.g., `msg.value / tx.gasprice`) * `encryptedArgs`: Array of encrypted bytes to decrypt * `plaintextArgs`: Array of unencrypted bytes to pass through **Returns:** `address` – The CTX supplicant address that will call `onDecrypt()` *** ### `IBiteSupplicant.onDecrypt(decryptedArgs, plaintextArgs)` Callback that your contract must implement. Called by SKALE consensus with decrypted data. **Parameters:** * `decryptedArgs`: Array of decrypted byte arrays * `plaintextArgs`: Array of plaintext byte arrays (passed through) **Example:** ```solidity theme={null} pragma solidity >=0.8.27; import { BITE } from "@skalenetwork/bite-solidity/BITE.sol"; import { IBiteSupplicant } from "@skalenetwork/bite-solidity/interfaces/IBiteSupplicant.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; contract MyContract is IBiteSupplicant { using Address for address payable; address public ctxSender; function revealSecret(bytes calldata encrypted) external payable { bytes[] memory encryptedArgs = new bytes[](1); encryptedArgs[0] = encrypted; bytes[] memory plaintextArgs = new bytes[](0); ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, msg.value / tx.gasprice, encryptedArgs, plaintextArgs ); payable(ctxSender).sendValue(msg.value); } function onDecrypt( bytes[] calldata decryptedArgs, bytes[] calldata ) external override { require(msg.sender == ctxSender, "Unauthorized"); // Handle decrypted data } } ``` *** ## Transaction Structure ### BITE Encrypted Transaction Format BITE transactions modify standard Ethereum transaction structure: ```typescript theme={null} interface BiteTransaction { to: string; // Always set to BITE magic address data: string; // Encrypted payload containing [EPOCH_ID, ENCRYPTED_DATA] from: string; // Sender address (unchanged) value: string; // ETH value (unchanged) gas: string; // Gas limit (must be set manually) gasPrice: string; // Gas price (unchanged) nonce: number; // Nonce (unchanged) } ``` ### Encryption Payload Structure The encrypted `data` field contains RLP-encoded array: ``` [EPOCH_ID, ENCRYPTED_BITE_DATA] ``` * `EPOCH_ID`: Current committee epoch * `ENCRYPTED_BITE_DATA`: Contains the encrypted transaction data (AES-encrypted payload with BLS-encrypted AES key) > **Important: Gas Limit Requirement** > When passing a transaction to `bite.ts`, you must set `gasLimit` manually. `estimateGas` does not return proper values for encrypted transactions. If `gasLimit` is omitted, `bite.ts` will automatically set it to **300000**. *** ## Common Use Cases ### 1. Private Token Transfers ```typescript theme={null} // Encrypt ERC20 transfer const transferData = iface.encodeFunctionData('transfer', [recipient, amount]); const tx = { to: tokenAddress, data: transferData, gasLimit: 200000 }; const encryptedTx = await bite.encryptTransaction(tx); ``` ### 2. Confidential Contract Interactions ```typescript theme={null} // Encrypt any contract call const callData = iface.encodeFunctionData('confidentialFunction', [secretParam]); const tx = { to: contractAddress, data: callData, gasLimit: 300000 }; const encryptedTx = await bite.encryptTransaction(tx); ``` ### 3. Raw Message Encryption ```typescript theme={null} // Encrypt raw hex data directly const message = '0x1234567890abcdef'; const encryptedMessage = await bite.encryptMessage(message); console.log('Encrypted:', encryptedMessage); ``` ### 4. Cached Committee Encryption ```typescript theme={null} // Fetch committee info once, reuse for multiple transactions const committees = await bite.getCommitteesInfo(); // Encrypt multiple transactions without additional RPC calls const tx1 = await BITE.encryptTransactionWithCommitteeInfo(transaction1, committees); const tx2 = await BITE.encryptTransactionWithCommitteeInfo(transaction2, committees); ``` ### 5. Committee Rotation Monitoring ```typescript theme={null} // Monitor for upcoming rotations const committees = await bite.getCommitteesInfo(); if (committees.length === 2) { console.log('Rotation in progress'); // Implement rotation-aware logic } ``` # Conditional Transactions Source: https://docs.skale.space/developers/bite-protocol/conditional-transactions Learn about Conditional Transactions (CTX) with BITE Protocol on SKALE Conditional Transactions are the second privacy primitive part of SKALE's BITE (Blockchain Integrated Threshold Encryption) Protocol. They enable smart contracts to store encrypted data and request decryption directly from within Solidity and the EVM. While Encrypted Transactions focuses on encrypting transaction payloads for privacy, Conditional Transactions (CTXs) are transactions initiated by smart contracts in one block and executed in the next block with decrypted data. This example uses the BITE V2 Sandbox 2. Contact the SKALE Team in [https://discord.gg](https://discord.gg) for access. ## How CTX Works 1. **Encrypt off-chain:** Use `bite.encryptMessage()` to encrypt data 2. **Submit to contract:** Send encrypted data to a smart contract 3. **Request decryption:** Contract calls `BITE.submitCTX()` to create a Conditional Transaction 4. **Next block:** SKALE consensus decrypts and calls `onDecrypt()` callback with decrypted values ## Prerequisites * Node.js 18+ and bun or npm * `@skalenetwork/bite` TypeScript SDK * `@skalenetwork/bite-solidity` Solidity helpers * Access to BITE V2 Sandbox 2 **Compiler Requirements** * Solidity version: **>= 0.8.27** * EVM version: **istanbul** or lower (via `pragma` or compiler settings) ## Project Setup ```bash theme={null} mkdir ctx-example && cd ctx-example npm init -y npm i --save-dev hardhat npx hardhat init # Install BITE dependencies npm i @skalenetwork/bite @skalenetwork/bite-solidity @openzeppelin/contracts ``` ```bash theme={null} mkdir ctx-example && cd ctx-example forge init # Install BITE dependencies forge install skalenetwork/bite-solidity openzeppelin/openzeppelin-contracts # Add to remappings.txt echo "@skalenetwork/bite-solidity/=lib/bite-solidity/contracts" >> remappings.txt echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts" >> remappings.txt ``` ## Smart Contract Create `SimpleSecret.sol`: ```solidity theme={null} pragma solidity >=0.8.27; import { Address } from "@openzeppelin/contracts/utils/Address.sol"; import { BITE } from "@skalenetwork/bite-solidity/BITE.sol"; import { IBiteSupplicant } from "@skalenetwork/bite-solidity/interfaces/IBiteSupplicant.sol"; contract SimpleSecret is IBiteSupplicant { using Address for address payable; bytes public decryptedMessage; address public ctxSender; uint256 public constant CTX_GAS_LIMIT = 2500000; uint256 public constant CTX_GAS_PAYMENT = 0.06 ether; error AccessViolation(); // Submit encrypted secret for decryption function revealSecret(bytes calldata encrypted) external payable { require(msg.value == CTX_GAS_PAYMENT, "Invalid CTX gas payment"); bytes[] memory encryptedArgs = new bytes[](1); encryptedArgs[0] = encrypted; bytes[] memory plaintextArgs = new bytes[](0); // Submit CTX - returns address that will call onDecrypt address payable ctxSender = BITE.submitCTX( BITE.SUBMIT_CTX_ADDRESS, CTX_GAS_LIMIT, encryptedArgs, plaintextArgs ); // Refund any unused ETH payable(ctxSender).sendValue(msg.value); } // Called by SKALE consensus in next block with decrypted data function onDecrypt( bytes[] calldata decryptedArgs, bytes[] calldata /* plaintextArgs */ ) external override { decryptedMessage = decryptedArgs[0]; } function getSecret() external view returns (bytes memory) { return decryptedMessage; } receive() external payable {} fallback() external payable {} } ``` ### Key Points * `IBiteSupplicant` requires implementing `onDecrypt()` callback * `BITE.submitCTX()` creates the Conditional Transaction * `BITE.SUBMIT_CTX_ADDRESS` is the predeployed CTX handler * `ctxSender` stores the address that will call back - used for security ## Test Script Create `run-simple.ts`: ```typescript theme={null} import { BITE } from "@skalenetwork/bite"; import { Contract, JsonRpcProvider, Wallet, ContractFactory } from "ethers"; import SimpleSecretJson from "./out/SimpleSecret.sol/SimpleSecret.json"; const providerUrl = "https://base-sepolia-testnet.skalenodes.com/v1/bite-v2-sandbox-2"; const INSECURE_ETH_PRIVATE_KEY = "0x..."; // Replace with your private key async function main() { const provider = new JsonRpcProvider(providerUrl); const signer = new Wallet(INSECURE_ETH_PRIVATE_KEY, provider); const bite = new BITE(providerUrl); const CTX_GAS_PAYMENT = BigInt(60000000000000000) // Deploy contract console.log("Deploying SimpleSecret..."); const factory = new ContractFactory( SimpleSecretJson.abi, SimpleSecretJson.bytecode, signer ); const contract = await factory.deploy(); await contract.waitForDeployment(); const address = await contract.getAddress(); console.log(`Contract: ${address}\n`); // Encrypt a secret message const secret = "0x" + Buffer.from("Hello BITE!").toString("hex"); console.log(`Encrypting: "${secret}"`); const encrypted = await bite.encryptMessage(secret); console.log(`Encrypted: ${encrypted}\n`); // Submit encrypted secret to contract console.log("Submitting encrypted secret..."); const tx = await contract.revealSecret(encrypted, { gasLimit: 500000, value: CTX_GAS_PAYMENT }); await tx.wait(); console.log("Submitted! Waiting for next block...\n"); // Wait for next block (onDecrypt executes) const currentBlock = await provider.getBlockNumber(); let nextBlock = currentBlock; while (nextBlock <= currentBlock) { await new Promise(resolve => setTimeout(resolve, 1000)); nextBlock = await provider.getBlockNumber(); } // Check decrypted result const result = await contract.getSecret(); const decoded = Buffer.from(result.slice(2), "hex").toString(); console.log(`Decrypted secret: "${decoded}"`); } main().catch(console.error); ``` ## Running ```bash theme={null} # Build contract npx hardhat compile # Run script bun run run-simple.ts ``` ```bash theme={null} # Build contract forge build # Run script bun run run-simple.ts ``` ## Resources * **BITE API Reference**: [BITE API](/developers/bite-protocol/bite-api-and-faqs) * **Repository**: [https://github.com/skalenetwork/bite-ts](https://github.com/skalenetwork/bite-ts) * **Solidity Helpers**: [https://github.com/skalenetwork/bite-solidity](https://github.com/skalenetwork/bite-solidity) * **How BITE Works**: [How BITE Works](/developers/bite-protocol/how-bite-works) * **Tutorial**: [Conditional Transactions Demo](/cookbook/privacy/conditional-transactions) # Encrypted Transactions Source: https://docs.skale.space/developers/bite-protocol/encrypted-transactions Explore how to create transactions that stay fully private until after execution with BITE Protocol on SKALE Encrypted transactions are the first privacy primitive released as part of SKALE's BITE Protocol. An encrypted transaction requires NO changes to existing Solidity contracts and works on all Solidity transactions. It encrypts the input `data` and `to` address of the EVM Transaction and requires a threshold decryption operation from the majority of the SKALE Chain validators to be decrypted and executed. Encrypted transactions are a great way to add an edge against MEV and help protect your onchain strategies from bad actors. ## Integration Examples ```typescript MetaMask theme={null} import { BITE } from '@skalenetwork/bite'; const bite = new BITE('https://your-skale-chain.skale.network'); async function sendEncryptedTransaction(to, data) { const tx = { to, data }; const encryptedTx = await bite.encryptTransaction(tx); const txHash = await window.ethereum.request({ method: 'eth_sendTransaction', params: [encryptedTx], }); return txHash; } ``` ```typescript Ethers.js theme={null} import { BITE } from '@skalenetwork/bite'; import { ethers } from 'ethers'; const bite = new BITE('https://your-skale-chain.skale.network'); const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); async function sendEncryptedERC20Transfer(tokenAddress, to, amount) { const iface = new ethers.Interface([ 'function transfer(address to, uint256 amount)' ]); const data = iface.encodeFunctionData('transfer', [to, amount]); const tx = { to: tokenAddress, data }; const encryptedTx = await bite.encryptTransaction(tx); const txResponse = await signer.sendTransaction(encryptedTx); return txResponse.hash; } ``` ## Resources * **Repository**: [https://github.com/skalenetwork/bite-ts](https://github.com/skalenetwork/bite-ts) * **How BITE Works**: [How BITE Works](/developers/bite-protocol/how-bite-works) * **Tutorial**: [Encrypted Transactions](/cookbook/privacy/encrypted-transactions) # BITE JS/TS SDK Source: https://docs.skale.space/developers/bite-protocol/typescript-sdk SDK for JavaScript and Typescript developers to build encrypted transactions on SKALE Typescript transpiles to JavaScript. The SDK is compatible with both JavaScript and Typescript in both Node.js, Bun, and browser environments. If you have any issues, please join us in [Discord](https://discord.gg/skale) to report the problem or open an issue on [GitHub](https://github.com/skalenetwork/bite-ts). ## Using the SDK The BITE JavaScript SDK enables application and wallet developers to encrypt EVM transactions. BITE extends SKALE's provably secure consensus protocol with threshold encryption, allowing developers to build private and confidential applications on SKALE. ### Installation ```bash theme={null} # npm npm i @skalenetwork/bite # yarn yarn add @skalenetwork/bite # pnpm pnpm add @skalenetwork/bite # Bun bun add @skalenetwork/bite ``` The SDK works with any BITE-enabled SKALE chain. Currently, BITE is available on [SKALE Base Mainnet and Testnet](/get-started/quick-start/skale-on-base) ONLY. ### Quick Start ```typescript theme={null} import { BITE } from '@skalenetwork/bite'; const providerUrl = 'https://your-fair-rpc'; const bite = new BITE(providerUrl); // Minimal tx object: encrypts `to` and `data` and rewrites `to` to BITE magic address const tx = { to: '0x1234567890abcdef1234567890abcdef12345678', data: '0x1234abcd', }; const encryptedTx = await bite.encryptTransaction(tx); // send via your wallet / provider, e.g. window.ethereum.request({ method: 'eth_sendTransaction', params: [encryptedTx] }) // Later, fetch revealed original fields after block finality const result = await bite.getDecryptedTransactionData(''); // result => { to: '0x...', data: '0x...' } ``` ## API Reference ### `new BITE(endpoint)` Creates a new BITE instance configured to use a specific BITE JSON-RPC endpoint. **Parameters:** * `endpoint: string` – BITE URL provider (JSON-RPC endpoint) **Example:** ```typescript theme={null} const bite = new BITE('https://your-skale-chain.skale.network'); ``` ### `bite.encryptTransaction(tx)` Encrypts a transaction object using the BLS threshold encryption public key(s) from the configured BITE provider. The encrypted transaction will have its `to` field set to the BITE magic address. **Parameters:** * `tx: { to: string; data: string; /* optional gas and other fields */ }` – Standard hex strings **Returns:** `Promise` – Encrypted params safe to submit to `eth_sendTransaction` When passing a transaction to `bite.ts`, it is necessary to set the gasLimit field manually. This is because estimateGas does not return a proper value for encrypted transactions. If gasLimit is omitted, `bite.ts` will automatically set it to **300000**. **Encryption Process:** 1. RLP encodes the original `data` and `to` fields 2. Encrypts the encoded data using AES with a randomly generated key 3. Encrypts the AES key using BLS threshold encryption 4. Creates the final payload in RLP format: `[EPOCH_ID, ENCRYPTED_BITE_DATA]` ### `bite.encryptMessage(message)` Encrypts a raw hex-encoded message using the BLS threshold encryption from the configured BITE provider. **Parameters:** * `message: string` – A hex string to encrypt (with or without `0x` prefix) **Returns:** `Promise` – An encrypted hex string in RLP format with epoch and encryption data **Example:** ```typescript theme={null} const encryptedMessage = await bite.encryptMessage('0x48656c6c6f20576f726c64'); // "Hello World" console.log('Encrypted:', encryptedMessage); ``` ### `bite.getDecryptedTransactionData(transactionHash)` Retrieves decrypted transaction data from the configured BITE provider using the `bite_getDecryptedTransactionData` JSON-RPC method. **Parameters:** * `transactionHash: string` – The transaction hash to decrypt **Returns:** `Promise` – JSON object with `data` and `to` keys containing the original decrypted fields **Example:** ```typescript theme={null} const decryptedData = await bite.getDecryptedTransactionData('0x1234...abcd'); console.log('Original to:', decryptedData.to); console.log('Original data:', decryptedData.data); ``` This method only works for BITE transactions that have been processed and decrypted by the consensus. If the transaction doesn't exist or has no decrypted data, an error is thrown. ### `bite.getCommitteesInfo()` Fetches committee information from the configured BITE provider using the `bite_getCommitteesInfo` JSON-RPC method. **Returns:** `Promise` – An array of 1-2 JSON objects, each containing: * `commonBLSPublicKey`: A 256-character hex string (128-byte BLS public key) * `epochId`: An integer representing the epoch identifier **Array Contents:** * **1 element**: During normal operation (single active committee) * **2 elements**: During committee rotation periods (scheduled for next 3 minutes) **Example:** ```typescript theme={null} const committeesInfo = await bite.getCommitteesInfo(); console.log('Current BLS Public Key:', committeesInfo[0].commonBLSPublicKey); console.log('Current Epoch ID:', committeesInfo[0].epochId); if (committeesInfo.length === 2) { console.log('Rotation in progress - dual encryption active'); } ``` ## Best Practices ### Gas Limit Management Always set a proper gas limit for encrypted transactions. Do not rely on `estimateGas()` as it doesn't work correctly with encrypted payloads. ```typescript theme={null} // Bad - will use default 300000 gas const encryptedTx = await bite.encryptTransaction(tx); // Good - set appropriate gas limit const tx = { to: '0x...', data: '0x...', gasLimit: 200000, // Set appropriate limit for your transaction }; const encryptedTx = await bite.encryptTransaction(tx); ``` ### Monitoring Committee Changes ```typescript theme={null} const INTERVAL_MS = 30000; // 30 seconds // Check for upcoming committee rotations async function monitorCommitteeRotation() { const committees = await bite.getCommitteesInfo(); if (committees.length === 2) { console.warn('Committee rotation in progress - dual encryption active'); // Implement rotation-specific logic if needed } // Schedule periodic checks setTimeout(monitorCommitteeRotation, INTERVAL_MS); } ``` This is not a requirement for single use encrypted transactions. If you are building with Conditional Transactions, you can optionally monitor committee changes to handle expiration of a conditional transaction which can occur during # Appchain Source: https://docs.skale.space/developers/chain-types/appchain Understanding SKALE Appchains and their gas/compute model ## What is an Appchain? An appchain is a dedicated SKALE Chain that is fully dedicated to an individual application or a small subset of applications owned by a company or consortium. Appchains within the SKALE Network can be multiple different sizes and have different configurations. ## Key Characteristics * **Dedicated Resources**: Each appchain has dedicated compute resources allocated specifically for that application * **Customizable Configuration**: Appchains can be configured with different sizes, storage allocations, and other parameters * **Full Control**: Appchain owners have full control over access control, configuration, and chain management * **Native Features**: All appchains come with native bridge, RNG, filestorage (optional), oracle, and the chain itself ## Gas and Compute Model Appchains operate with zero gas fees by default, similar to all SKALE Chains. The compute resources are prepaid by the appchain owner through a subscription-based model. ## Related Topics * [Intro to SKALE Chains](/developers/run-a-skale-chain/intro-to-schains) - Learn more about running a SKALE Chain * [Pricing and Payments](/developers/run-a-skale-chain/pricing-and-payments) - Understanding appchain costs * [SKALE on Base](/get-started/quick-start/skale-on-base) - Get started with SKALE chains # Credit Chain Source: https://docs.skale.space/developers/chain-types/credit-chain Understanding SKALE Credit Chains and their gas/compute model ## What is a Credit Chain? A credit chain is a SKALE Chain model that operates on a credit-based system for gas and compute resources. This model allows for more flexible resource allocation and usage tracking. ## Key Characteristics * **Credit-Based System**: Resources are allocated based on credits rather than traditional gas fees * **Flexible Usage**: Credits can be managed and distributed according to the chain's needs * **Usage Tracking**: Detailed tracking of compute and gas resource consumption * **Prepaid Model**: Credits are typically prepaid by the chain owner or operator ## Gas and Compute Model Credit chains use a credit system where compute resources are measured in credits. Users or applications consume credits when executing transactions or using compute resources. ## Related Topics * [Zero Gas Fees](/developers/gas-fees) - Understanding SKALE's gas fee model * [Intro to SKALE Chains](/developers/run-a-skale-chain/intro-to-schains) - Learn more about SKALE Chain types * [Pricing and Payments](/developers/run-a-skale-chain/pricing-and-payments) - Understanding chain costs # Zero Gas Fee / Gasless Chain Source: https://docs.skale.space/developers/chain-types/zero-gas-fee-gasless-chain Understanding zero gas fee and gasless transaction models on SKALE ## Zero Gas Fees SKALE Chains operate with **zero gas fees** by default. This means that the native gas token (sFUEL) has no monetary value and cannot be traded or sold. However, it still functions like Ether on Ethereum for compatibility with EVM tooling. ### Key Points * **No Monetary Value**: sFUEL cannot be purchased or sold * **EVM Compatible**: Works with all standard Ethereum tooling * **Spam Prevention**: Still prevents spam and DDoS attacks through computational requirements * **Free Forever**: Zero gas fees are permanent on all SKALE Chains ## Gasless Transactions SKALE also supports **gasless transactions**, which allow users to interact with the network without sFUEL in their wallet. This is achieved through a Proof of Work mechanism that generates "magic numbers" to validate transactions. ### How Gasless Transactions Work 1. The client generates a random 256-bit magic number using Proof of Work 2. The magic number is used as the gasPrice for the transaction 3. The transaction is validated based on the computational work performed 4. No sFUEL balance is required in the user's wallet ### Use Cases * **Invisible Transactions**: Create seamless user experiences without visible blockchain interactions * **True Decentralization**: No need for sFUEL distribution infrastructure * **Enhanced Security**: Reduces operational overhead for sFUEL distribution ## Comparison | Feature | Zero Gas Fees | Gasless Transactions | | --------------- | ------------------------------------------ | -------------------------------------------- | | Requires sFUEL | Yes (but free) | No | | User Experience | Invisible after initial sFUEL distribution | Completely invisible | | Implementation | Automatic | Requires Proof of Work | | Best For | Most applications | Applications requiring true decentralization | ## Related Topics * [Zero Gas Fees](/developers/gas-fees) - Detailed guide on SKALE's gas fee model * [sFUEL Distribution](/developers/run-a-skale-chain/managing-sfuel-distribution) - Managing sFUEL for your dApp * [Appchain](/developers/chain-types/appchain) - Learn about dedicated appchains * [Credit Chain](/developers/chain-types/credit-chain) - Learn about credit-based chains # Connect to SKALE Source: https://docs.skale.space/developers/integrate-skale/connect-to-skale Connect to the SKALE Network SKALE is a network of many chains. You can connect to SKALE Chains with a variety of tools and SDKs, just like you do other EVM chains. ## Connect to SKALE To connect via your command line interface (CLI), we recommend starting with [cast](https://getfoundry.sh/cast/overview), a command-line tool for interacting with Ethereum-based networks. To install cast, you should first install [Foundry](https://docs.tempo.xyz/sdk/foundry#get-started-with-foundry). ```bash theme={null} # Check block height cast block-number --rpc-url https://skale-base.skalenodes.com/v1/base ``` ## SKALE Chains The following are active sChains. SKALE Chains are built on different base networks - either Base or Ethereum. The bridge mechanism depends on the base network: * **Base chains**: Built on Base L2 with native bridge to/from Base * **Ethereum chains**: Built on Ethereum with IMA (SKALE Interchain Messaging Agent) bridge to/from Ethereum ### SKALE Base Chains *Built on Base L2 with native bridge to/from Base* #### SKALE Base Mainnet | Network Name | SKALE Base | | ------------ | ------------------------------------------------------------------------------------------ | | RPC Url | [https://skale-base.skalenodes.com/v1/base](https://skale-base.skalenodes.com/v1/base) | | WSS Url | wss\://skale-base.skalenodes.com/v1/ws/base | | Chain Id | 1187947933 | | Chain Id Hex | 0x46cea59d | | Explorer | [https://skale-base-explorer.skalenodes.com/](https://skale-base-explorer.skalenodes.com/) | | Portal | [https://base.skalenodes.com/chains/base](https://base.skalenodes.com/chains/base) | #### SKALE Base Testnet | Network Name | SKALE Base Sepolia | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | RPC Url | [https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha](https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha) | | Chain Id | 324705682 | | Chain Id Hex | 0x135A9D92 | | Explorer | [https://base-sepolia-testnet-explorer.skalenodes.com/](https://base-sepolia-testnet-explorer.skalenodes.com/) | | Faucet | [https://base-sepolia-faucet.skale.space](https://base-sepolia-faucet.skale.space) | ### Ethereum-based Chains *Built on Ethereum with IMA (Interchain Messaging Agent) bridge to/from Ethereum* #### Calypso Mainnet | Network Name | Calypso Hub | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | RPC Url | [https://mainnet.skalenodes.com/v1/honorable-steel-rasalhague](https://mainnet.skalenodes.com/v1/honorable-steel-rasalhague) | | Chain Id | 1564830818 | | Explorer | [https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com/](https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com/) | | Portal | [https://portal.skale.space/chains/calypso](https://portal.skale.space/chains/calypso) | #### Calypso Testnet | Network Name | Calypso Testnet | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | RPC Url | [https://testnet.skalenodes.com/v1/giant-half-dual-testnet](https://testnet.skalenodes.com/v1/giant-half-dual-testnet) | | Chain Id | 974399131 | | Explorer | [https://giant-half-dual-testnet.explorer.testnet.skalenodes.com/](https://giant-half-dual-testnet.explorer.testnet.skalenodes.com/) | | Portal | [https://testnet.portal.skale.space/chains/calypso](https://testnet.portal.skale.space/chains/calypso) | #### Europa Mainnet | Network Name | Europa Hub | | ------------ | -------------------------------------------------------------------------------------------------------------------- | | RPC Url | [https://mainnet.skalenodes.com/v1/elated-tan-skat](https://mainnet.skalenodes.com/v1/elated-tan-skat) | | Chain Id | 2046399126 | | Explorer | [https://elated-tan-skat.explorer.mainnet.skalenodes.com/](https://elated-tan-skat.explorer.mainnet.skalenodes.com/) | | Portal | [https://portal.skale.space/chains/europa](https://portal.skale.space/chains/europa) | #### Europa Testnet | Network Name | Europa Testnet | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | RPC Url | [https://testnet.skalenodes.com/v1/juicy-low-small-testnet](https://testnet.skalenodes.com/v1/juicy-low-small-testnet) | | Chain Id | 1444673419 | | Explorer | [https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/](https://juicy-low-small-testnet.explorer.testnet.skalenodes.com/) | | Portal | [https://testnet.portal.skale.space/chains/europa](https://testnet.portal.skale.space/chains/europa) | #### Nebula Mainnet | Network Name | Nebula Hub | | ------------ | ------------------------------------------------------------------------------------------------------------------------------ | | RPC Url | [https://mainnet.skalenodes.com/v1/green-giddy-denebola](https://mainnet.skalenodes.com/v1/green-giddy-denebola) | | Chain Id | 1482601649 | | Explorer | [https://green-giddy-denebola.explorer.mainnet.skalenodes.com/](https://green-giddy-denebola.explorer.mainnet.skalenodes.com/) | | Portal | [https://portal.skale.space/chains/nebula](https://portal.skale.space/chains/nebula) | #### Nebula Testnet | Network Name | Nebula Testnet | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | RPC Url | [https://testnet.skalenodes.com/v1/lanky-ill-funny-testnet](https://testnet.skalenodes.com/v1/lanky-ill-funny-testnet) | | Chain Id | 37084624 | | Explorer | [https://lanky-ill-funny-testnet.explorer.testnet.skalenodes.com/](https://lanky-ill-funny-testnet.explorer.testnet.skalenodes.com/) | | Portal | [https://testnet.portal.skale.space/chains/nebula](https://testnet.portal.skale.space/chains/nebula) | # EVM Differences Source: https://docs.skale.space/developers/integrate-skale/evm-differences SKALE vs Ethereum EVM comparison SKALE has been designed to maintain core compatiblity with Ethereum while differing on some core mechanics and newer features that may be unncessary due to SKALE's design or not yet integrated due their introduction through more recent Ethereum hardforks. SKALE allows developers to build, deploy, and interact with smart contracts and applications using the same tools, languages, and frameworks they use on Ethereum. While the execution environment mirrors Ethereum's, SKALE introduces key differences optimized for performance, scalability, AI, and privacy. ## Key Protocol Differences ### Gas Fees & Economics **SKALE**: Zero gas fees by default with compute credits or sFUEL. Gasless transactions with *Proof-of-Work* and sFUEL-based chains. **Ethereum**: Variable gas fees paid in ETH, market-based pricing with EIP-1559 base fees + priority fees. SKALE has multiple types of chains due to the multichain network design including appchains, credit chains, and the ability to offer chains with a gas fee if chain buyers want them. ### Block Production & Finality **SKALE**: Target 1-second block times with instant finality. Transactions are immutable after inclusion in a single block. **Ethereum**: 12-second block times with probabilistic finality. Full finality requires \~12-15 minutes of validator confirmations though two checkpoints. ### Resource Limits **SKALE**: \~268M block gas limit and 64KB contract size limit allowing for more complex smart contracts **Ethereum**: \~60M block gas limit (variable, as of 12/1/2025), 24KB contract size limit, strict constraints for network-wide throughput. ### Payments in Solidity Unless the SKALE Chain implements a native gas fee token with value, zero gas fee chains should avoid the use of `msg.value` in smart contracts and instead use ERC-20 or other tokenization options to handle payments. All SKALE Chains currently in prou ### Contract Deployment SKALE Hubs on Ethereum -- Calypso, Europa, Nebula, and Titan -- currently have contract deployment locked to an "allowlist". This means that by default to deploy access needs to be granted to either a) the EOA/tx.origin, b) a smart contract (which then allows anyone to deploy through it), c) A specific account on a set of contracts (strengthening security for contracts that could cause state pollution). The first public chain on the SKALE Base deployment has open contract deployment and simply requires the purchasing of compute credits. Credits can be purchased on the [SKALE Portal](https://base.skalenodes.com/credits). This means that SKALE on Ethereum differs considerably from Ethereum as of 12/20/2025, however, this could change with the upgrade to the credit system in the near future. # Gas Fees Source: https://docs.skale.space/developers/integrate-skale/gas-fees Explore Zero Gas Fees, Compute Credits, Gas Chains, and Gasless Transactions on SKALE Since Day 1, one of SKALE's most innovative features has been zero gas fees. Over the years, the developer ecosystem has continued to evolve and we now have a number of incredible technical options when it comes to having (or not) gas fees. This page breaks down the various options and trade offs for businesses and developers looking to adopt SKALE. ## Zero Gas Fees with sFUEL The original implementation of zero gas fees -- activley in use across many SKALE Chains on Ethereum -- is a unique gas structure where there is a gas token for compatibility but it by default has no value (i.e valueless gas). sFUEL allows the SKALE Chain to maintain 100% EVM compatiblity with popular tools and wallets while making it simple to scale usage for high performance applications. sFUEL is technically equivalent to ETH on Ethereum but is pre-minted with a massive surplus and is constantly recycled back into a contract allowing redistribution. This is by design ensuring that the supply of sFUEL for the chain operators should never run out. Developers and users can acquire sFUEL on the SKALE Hubs from [https://sfuelstation.com](https://sfuelstation.com) # Zero Gas Fees with Compute Credits A new model put into place as part of SKALE Expand, compute credits are philisophically designed to help simplify the cost of compute for developers in a blockchain. Technically, compute credits are very similar to sFUEL, however with some small nuances: 1. Compute credits are directly purchasable, while sFUEL is not 2. The cost of compute credits can be dynamic over time, however aims to be as stable as possible to represent the flat cost of compute 3. The ability to acquire compute credits as needed allows for more open-ecosystems ## Gasless Transactions with Proof-of-Work Gasless transactions utilize the proof-of-work scheme -- an algorithm designed to find a magic number to satisfy some equation -- which can then be used to send transactions without having any sFUEL in the wallet. Proof-of-Work is disabled by default on credit chains. ## Gas Chains A gas chain is simple and acts similarly to a Layer 2 in setup. The team launching the chain can chose a token available on the network the sChain is being launched from i.e Ethereum or Base and have that token -- SKL, ETH, WBTC, custom ERC-20, etc -- be chosen as the gas token on the SKALE Chain. During setup, the gas asset on the sChain then has a dynamic supply based on the bridged in asset. # JSON-RPC API Source: https://docs.skale.space/developers/integrate-skale/json-rpc-api SKALE JSON-RPC Compatibility SKALE supports Ethereum JSON-RPC API with compatibility across different node types: | Method | Core | Full | Archive | | --------------------------- | --------- | --------- | --------- | | `eth_blockNumber` | Supported | Supported | Supported | | `eth_call` | Partial | Partial | Partial | | `eth_estimateGas` | Supported | Supported | Supported | | `eth_getBalance` | Partial | Partial | Supported | | `eth_getBlockByNumber` | Supported | Supported | Supported | | `eth_getTransactionByHash` | Supported | Supported | Supported | | `eth_getTransactionReceipt` | Supported | Supported | Supported | | `eth_sendRawTransaction` | Supported | Supported | Supported | | `eth_getCode` | Partial | Partial | Supported | | `eth_getStorageAt` | Partial | Partial | Supported | | `eth_getTransactionCount` | Partial | Partial | Supported | | `eth_gasPrice` | Supported | Supported | Supported | | `eth_getLogs` | Partial | Partial | Supported | | `eth_newFilter` | Partial | Partial | Supported | | `eth_subscribe` | Supported | Supported | Supported | | `net_version` | Supported | Supported | Supported | | `web3_clientVersion` | Supported | Supported | Supported | **BITE Protocol Methods:** * `bite_getDecryptedTransactionData` - Get decrypted transaction data * `bite_getCommitteesInfo` - Get committee information **Legend:** * Supported: Full support * Partial: Partial support (limitations apply) Most Core and Full nodes only support "latest" state queries. Use Archive nodes for historical data. SKALE has no uncle blocks, mining, or peer discovery. Related methods return default values. # Access Control Source: https://docs.skale.space/developers/run-a-skale-chain/access-control Managing Control and Access to a SKALE Chain While SKALE Chains themselves are Layer 1 blockchains, the architectural design intentionally allows for a semi-permissioned access control layer that is manageable by the chain owners or operators. This allows for maximum flexibility where an individual chain can be locked down to only allow a single application or group of applications to deploy, be 100% public for anyone, or allow various factories to be used by different wallets. Access Control to a SKALE Chain is managed by the Config Controller smart contract which is predeployed on a SKALE Chain. Config Controller makes use of [OpenZeppellin's Access Control](https://openzeppelin.com) to handle roles, events, and management automatically. ## Access via Accounts The most straightforward and common way to access a SKALE Chain for deployment is by having an externally owned account (EOA) added to the chain allow list. An account that is added to this list is given `DEPLOYER_ROLE` which allows it to directly bypass all restrictions and deploy ANY smart contract to a SKALE Chain as long as it fits within the block gas limit. See [how to add address to whitelist](#add-to-whitelist) and [how to remove from whitelist](#remove-from-whitelist) to understand how to manage your access control layer. ## Access via Factories Another common method for deployment is via factories. SKALE Chain owners and operators can deploy popular generic factories such as CREATE2Factory, SingletonFactory, and CreateX to their chain and allow developers to use them when deploying to their chain. Additionally, developers themselves can create contracts to deploy other contracts making specific factories for their uses. ### Open Factory Usage Open factory usage is where the SKALE Chain owner or operator grants `DEPLOYER_ROLE` to a smart contract that is acting as the factory. This then allows ANY account or smart to use the whitelisted factory to deploy a smart contract through. **Example: SushiSwap** On the [SKALE Europa Hub](https://portal.skale.space/chains/europa), SushiSwap which is based off of Uniswap; has a factory contract that creates liquidity pairs. This contract is dedicated to SushiSwap and allows for liquidity pairs (which are smart contracts) to be created by anyone via the UniswapV2/V3 factories that are deployed as part of SushiSwap's core architecture. This then allows anyone to deploy a smart contract via the factory regardless of their direct status on the allow list. ### Closed Factory Usage Closed factory usage is where the SKALE Chain owner or operator allows a specific account or set of accounts to deploy through a factory. The unique part about this design is: 1. The accounts whitelisted on the specific factory **DO NOT** have the ability to deploy directly to the blockchain or through other factories 2. Accounts provided with `DEPLOYER_ROLE` generally on the blockchain can deploy via the contract IF the contract allows generic deployment rights 3. Accounts without `DEPLOYER_ROLE` cannot deploy through the factory IF the factory does not have `DEPLOYER_ROLE` directly; sufficiently securing the contract and chain from spam ## Public Access SKALE Chains by default come with the permission layer enabled. However, the permission layer can be enabled/disabled at any time by the SKALE Chain owner or operator. You can enable and disable the permission layer through the Config Controller smart contract. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const CONFIG_CONTROLLER_ABI = [ "function enableFreeContractDeployment() external", "function disableFreeContractDeployment() external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); // Allow anyone to deploy const res = await contract.enableFreeContractDeployment(); // Disable deployments, role based allowances are enabled const res = await contract.disableFreeContractDeployment(); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` ## Managing Access ### Add to Whitelist The following is how to add an address to the SKALE Chain whitelist: The `msg.sender` must have `DEPLOYER_ADMIN_ROLE` to call this successfully. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const WALLET_TO_WHITELIST = "0x..."; const CONFIG_CONTROLLER_ABI = [ "function addToWhitelist(address addr) external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); const res = await contract.addToWhitelist(WALLET_TO_WHITELIST); console.log(`Wallet ${WALLET_TO_WHITELIST} whitelisted on SKALE Chain at ${res}`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` ### Remove from Whitelist The following is how to remove an address from the SKALE Chain whitelist: The `msg.sender` must have `DEPLOYER_ADMIN_ROLE` to call this successfully. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const WALLET_TO_REMOVE = "0x..."; const CONFIG_CONTROLLER_ABI = [ "function removeFromWhitelist(address addr) external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); const res = await contract.removeFromWhitelist(WALLET_TO_REMOVE); console.log(`Wallet ${WALLET_TO_REMOVE} removed on SKALE Chain at ${res}`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` ### Add Admin for Contract The following is how to add an admin which can manage a smart contract whitelist: The `msg.sender` must have `DEPLOYER_ADMIN_ROLE` to call this successfully. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const ADMIN_WALLET_ADDRESS = "0x..."; // your eth address const FACTORY_ADDRESS = "0x..."; // the contract deploying other contracts const CONFIG_CONTROLLER_ABI = [ "function addAllowedOriginRoleAdmin(address admin, address deployer) external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); const res = await contract.addAllowedOriginRoleAdmin(ADMIN_WALLET_ADDRESS, FACTORY_ADDRESS); console.log(`Wallet ${ADMIN_WALLET_ADDRESS} can now manage ${FACTORY_ADDRESS} on SKALE Chain at ${res}`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` ### Remove Admin from Contract Whitelist The following is how to remove an admin from managing a smart contract whitelist: The `msg.sender` must have `DEPLOYER_ADMIN_ROLE` to call this successfully. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const ADMIN_WALLET_ADDRESS = "0x..."; // your eth address const FACTORY_ADDRESS = "0x..."; // the contract deploying other contracts const CONFIG_CONTROLLER_ABI = [ "function removeAllowedOriginRoleAdmin(address admin, address deployer) external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); const res = await contract.removeAllowedOriginRoleAdmin(ADMIN_WALLET_ADDRESS, FACTORY_ADDRESS); console.log(`Wallet ${ADMIN_WALLET_ADDRESS} can no longermanage ${FACTORY_ADDRESS} on SKALE Chain at ${res}`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` ### Add to Contract Whitelist The following is how to add an address to the SKALE Chain whitelist: The `msg.sender` must have `allowedOriginRoleAdmin(deployer)` to call this successfully. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const WALLET_TO_WHITELIST = "0x..."; const FACTORY_ADDRESS = "0x..."; const CONFIG_CONTROLLER_ABI = [ "function allowOrigin(address transactionOrigin, address deployer) external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); const res = await contract.allowOrigin(WALLET_TO_WHITELIST, FACTORY_ADDRESS); console.log(`Wallet ${WALLET_TO_WHITELIST} whitelisted on SKALE Chain at ${res}`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` ### Remove from Contract Whitelist The following is how to remove an address from the SKALE Chain whitelist: The `msg.sender` must have `allowedOriginRoleAdmin(deployer)` to call this successfully. ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet } from "ethers"; const WALLET_TO_WHITELIST = "0x..."; const FACTORY_ADDRESS = "0x..."; const CONFIG_CONTROLLER_ABI = [ "function forbidOrigin(address transactionOrigin, address deployer) external" ]; const CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS = "0xD2002000000000000000000000000000000000d2"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(CONFIG_CONTROLLER_PREDEPLOYED_ADDRESS, CONFIG_CONTROLLER_ABI, wallet); const res = await contract.forbidOrigin(WALLET_TO_WHITELIST, FACTORY_ADDRESS); console.log(`Wallet ${WALLET_TO_WHITELIST} can no longer deploy via ${FACTORY_ADDRESS} on SKALE Chain at ${res}`); } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` # Customize SKALE Chain Source: https://docs.skale.space/developers/run-a-skale-chain/customize-schain Explore the various configuration and features available to configure on your SKALE Chain This page explores the various ways that SKALE Chains can be configured. Want to customize your chain in a way not found here? Head over to the [SKALE Forum](https://forum.skale.network) and share your ideas or join the SKALE team in [Discord](https://discord.gg/skale) to share your idea(s)! ## State Configuration One of the most important decisions made when creating a new SKALE Chain is where the state is going to be allocated. There are three areas that node storage is allocatable too: 1. **Consensus Database**: the area of the node responsible for storing block data 2. **Contract Storage**: the area of the node that stores active contract state 3. **Filestorage**: the area of the node responsible for storing files uploaded or replicated via SKALE Filestorage The following configurations are available: SKALE Chain Storage Key SKALE Chain Storage Options ## Transaction Processing SKALE Chains have two options when it comes to transaction processing. * **Standard Processing**: allows for a single transaction per account/wallet per block. Great for chains interested matching Ethereum's transaction processing architecture * **Multi-transaction Mode (Recommended)**: great for chains catering to high-throughput applications # Intro to SKALE Chains Source: https://docs.skale.space/developers/run-a-skale-chain/intro-to-schains Introduction to SKALE Chain Technology The SKALE Network is home to a unique type of decentralized and distributed compute system known as a SKALE Chain. Uniquely designed to offer both permissioned and permissionless compute; SKALE Chains are themselves are Layer 1 blockchains with additional layers of modular management and functionality enabling them to exist as an Appchain or as an ecosystem chain like a hub. When a new SKALE Chain is created, the owner of the chain is provided elevated privileges which they can use to setup the chain to their unique needs. Privileges can be provided to others, secured via multisigs, or renounced entirely. This section of the documentation explores a variety of critical operations that should be taken when a new SKALE Chain is created. ## SKALE Chain Setup Guide The following is the step-by-step operation for SKALE Chain setup and creation: 1. Follow the [Setup Requirements](/developers/run-a-skale-chain/setup-requirements) 2. Determine which features you want to [customize on your SKALE Chain](/developers/run-a-skale-chain/customize-schain) 3. SKALE Chain Creation. Join us in [Discord](https://discord.gg/skale) to request a SKALE Chain # Managing sFUEL Distribution Source: https://docs.skale.space/developers/run-a-skale-chain/managing-sfuel-distribution Options for distributing sFUEL to developers and users of your SKALE Chain The default gas token of a SKALE Chain is SKALE Fuel (sFUEL). This gas token allows for a SKALE Chains to be fully Ethereum Virtual Machine (EVM) compatible while also offering [zero gas fees](/developers/gas-fees) to developers and end-users. While gasless transactions are recommended for most applications due to simple management; SKALE Chains running with sFUEL active will need to ensure developers and/or end-users can access the gas token to execute transactions. ## Etherbase For SKALE Chain owners and operators, accessing sufficient sFUEL is critical. All SKALE Chains come predeployed with a smart contract called **Etherbase** which is important for two reasons: 1. A large allocation of sFUEL is stored in this contract upon creation of the SKALE Chain, ensuring sufficient quantities are available for operators 2. sFUEL when consuemd in a transaction is automatically recycled into Etherbase ensuring that a SKALE Chain doesn't run out of gas ```ts theme={null} // Using Ethers v6 import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; const ETHERBASE_ABI = [ "function partiallyRetrieve(address payable receiver, uint256 amount) external", ]; const ETHERBASE_PREDEPLOYED_ADDRESS = "0xd2bA3e0000000000000000000000000000000000"; async function main() { const provider = new JsonRpcProvider("https://mainnet.skalenodes.com/v1/"); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(ETHERBASE_PREDEPLOYED_ADDRESS, ETHERBASE_ABI, wallet); // Allow anyone to deploy const res = await contract.partiallyRetrieve(wallet.address, parseEther("25")); console.log("Res: ", res); // Transaction Hash, once confirmed wallet.address native balance increases by 25 } main() .catch((err) => { console.error(err); process.exitCode = 1; }); ``` If you encouter any errors when trying to retrieve gas from Etherbase; this likely means that the address is missing `ETHER_MANAGER_ROLE`. ## sFUEL Station SKALE Chains can request being added to the [sFUEL Station](https://sfuelstation.com). The sFUEL Station is currently availalable on SKALE Hub testnet and mainnet Chains: Calypso, Europa, Nebula, and Titan. The sFUEL Station allows users to put in their wallet and attain a small allocation of sFUEL to get started on a particular SKALE Chain. While your chain may be added to the sFUEL Station upon request, this should not be a primary way to onboard users as it can increase the complexity of onboarding. You should utilize sFUEL Distribution within dApps when possible. # Pricing and Payments Source: https://docs.skale.space/developers/run-a-skale-chain/pricing-and-payments Pricing information and payment flows for SKALE Chains Having a dedicated SKALE Chain is similar to having dedicated resources on a cloud provider. Similar to your cloud provider, you have expenses owed in return for the compute provided. SKALE has a unique pricing structured that is governed by the [SKALE DAO](https://snapshot.box/#/s:skale.eth). ## SKALE Chain Types The modular design of SKALE Supernodes allows for a single supernode to support many SKALE Chains of varying sizes. The SKALE Network currently has one official size accepted by the DAO and a second size actively in discussion. | Chain Name | Node Resources | Cost per Month\* | | ----------------- | -------------- | ---------------- | | Hub Chain | 1/8 | \$7200\* | | Performance Chain | 1/32 | \$7200\* | ## Pricing Model SKALE utilizes a subscription-based pricing model, similar to cloud computing, unlike most blockchains that use pay-per-transaction gas fees. **Key Benefits** * Flat Fees - fees are predictable based on the SKALE Chain Size. This ensures stability and easy to predicte operating expenses * Top Tier Unit Economics - with fees being prepaid and all usage being free, the cost per transaction drastically decreases * Improved UX - users shouldn't suffer or have to jump through a dozen hoops (KYC, Buying Tokens, etc.) just to use your chain and/or application. Prepaid blockchains make free onboarding or freemium experiences a reality in Web3. * Optional Prepayment - chain owners can easily pre-pay for their SKALE Chain for up to 24 months to lock in the current pricing ## How to Pay **Notes** * Payments occur on the [SKALE Europa Hub](https://portal.skale.space/chains/europa) and are due before on the 1st of each month. * The official paymaster smart contract is `[0x0d66cA00CbAD4219734D7FDF921dD7Caadc1F78D](https://elated-tan-skat.explorer.mainnet.skalenodes.com/address/0x0d66cA00CbAD4219734D7FDF921dD7Caadc1F78D)` ### Payment Guide 1. Head over to [https://portal.skale.space/chains](https://portal.skale.space/chains) 2. Select your SKALE Chain 3. Click **Manage** Click Manage on your SKALE Chain 5. Connect a Wallet 6. Input the amount of months you want to pay for 7. Follow the prompts, bridge SKL tokens if needed, and pay for your SKALE Chain # SKALE Chain Ownership Source: https://docs.skale.space/developers/run-a-skale-chain/schain-ownership A deep dive into the ownership structure of a SKALE Chain ## SKALE Chain Ownership Structure By default, on all SKALE V2 Chains; the SKALE Chain ownership structure is a unique combination of predeployed smart contracts, multisignature wallets (across multiple networks), and cold wallets. The structure looks like the following: \-- Ownership Structure Text, Replace with Diagram There is a smart contract predeployed on every SKALE Chain that is created called **Marionette**. Marionettee is essentially a simple smart contract that has proxied access control and is by default given `DEFAULT_ADMIN_ROLE` on other predeployed smart contracts on the SKALE Chain like Etherbase, Filestorage, Config Controller, etc. Marionette has a role called `PUPPETTEER_ROLE` which allows others to execute dynamic smart contract calls through it. By default, when the chain is created there are two addresses given `PUPPETTEER_ROLE`: 1. The local multisignature wallet deployed on the SKALE Chain which is also a predeployed smart contract 2. The external Gnosis SAFE smart contract address deployed on Ethereum Mainnet which is generally considered to be the "SKALE Chain Owner" Additionally, by default in this setup MessageProxyForSchain is provided with IMA\_ROLE and is needed to allow the Gnosis SAFE on Ethereum Mainnet to post data all the way to Marionette. # Setup Requirements Source: https://docs.skale.space/developers/run-a-skale-chain/setup-requirements Requirements for setting up a SKALE Chain The following are subject to change. For information on changing requirements, you can refer to the [SKALE DAO](https://snapshot.box/#/s:skale.eth) or contact the SKALE team in [Discord](https://discord.gg/skale). ## Requirements 1. A **[Gnosis SAFE](https://safe.global/)** address on the Ethereum Mainnet, dedicated to SKALE Chain Ownership 2. A **cold wallet** address from a member of the ownership or management group who is technical enough to utilize a cold wallet 3. Send 0.01 ETH to your Gnosis SAFE address 4. Determine the [configurations](/developers/run-a-skale-chain/customize-schain) for how you want to customize your SKALE Chain 5. Reach out to the SKALE Team in [Discord](https://discord.gg/skale) for support creating your SKALE Chain # Using Multisig Wallet CLI Source: https://docs.skale.space/developers/run-a-skale-chain/using-multisig-wallet-cli Explore how to use the multisigwallet CLI to manage your SKALE Chain The [multisigwallet CLI](https://github.com/skalenetwork/multisigwallet-cli) is a command line interface tool that helps handle encoding function data specifically on SKALE Network contracts. Due to the unique [ownership structure](/developers/run-a-skale-chain/schain-ownership) of a SKALE Chain, this CLI supports with management of your SKALE Chain both from the SKALE Chain side and from Mainnet via Gnosis SAFE. ## Installing the CLI? To install the CLI: 1. Visit the [releases](https://github.com/skalenetwork/multisigwallet-cli/releases), download the most recent version by clicking **Assets** and then downloading the zip. 2. Unpack the zip 3. Ensure [Yarn](https://yarnpkg.com/getting-started/install) is installed 4. Run `yarn install` in your command prompt You are now ready to use the CLI! ## Using Gnosis SAFE ### Encode Function Data Returns encoded data for interaction with SKALE Chain through gnosis safe on mainnet. ```bash theme={null} npx msig encodeData [options] [params...] ``` Required arguments: * `` - Destination SKALE Chain name * `` - Destination contract that you wanna call * `` - Function that you wanna call on the destination contract Optional arguments: * `[params...]` - Arguments for the destination function To execute via SAFE, see [using SAFE](/developers/run-a-skale-chain/using-safe). ## Using the MultisigWallet on SKALE Chain The following calls require a PRIVATE\_KEY and RPC to be set in .env or passed in via CLI to work. The `.env` method is encouraged. The below calls execute directly through the SKALE Chain MultiSigWallet and DO NOT require the Gnosis SAFE on Ethereum Mainnet. ### Global options * `-a, --account ` - Account number from which the transaction should be performed, by default it's 1. The account is associated with a private key in `.env` * `--custom` - For custom abi, set filepath to ABI into `.env` ### Call Returns the result of executing the transaction, using call. ```bash theme={null} npx msig call [options] [params...] ``` Required arguments: * `` - Destination contract that you wanna call * `` - Function that you wanna call on the destination contract Optional arguments: * `[params...]` - Arguments for the destination function ### Recharge Allows to recharge the sFUEL balance of the MultiSigWallet contract ```bash theme={null} npx msig recharge [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Amount of sFUEL in wei ### Submit Transaction Allows an owner to submit and confirm a transaction. `` must be written in `PascalCase`. `` must be written in `camelCase` and function parameters must be written separated by spaces. ```bash theme={null} npx msig submitTransaction [options] [params...] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Name of the contract in pascal case * `` - Name of the function that you wanna call on the destination contract Optional arguments: * `[params...]` - Arguments for the destination function Usage example: ```bash theme={null} npx msig submitTransaction ConfigController addToWhitelist ``` ### Submit Transaction with Data Allows an owner to submit and confirm a transaction with custom data. ```bash theme={null} npx msig submitTransactionWithData [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Destination contract that you wanna call * `` - Encoded data of function selector and params ### Confirm Transaction Allows an owner to confirm a transaction. ```bash theme={null} npx msig confirmTransaction [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Transaction id ### Revoke Confirmation Allows an owner to revoke a confirmation for a transaction. ```bash theme={null} npx msig revokeConfirmation [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Transaction id ### Execute Transaction Allows an owner on the MultisigWallet to execute a confirmed transaction. ```bash theme={null} npx msig executeTransaction [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Transaction id ### Get Confirmations Returns a list (i.e array) with the owner addresses who confirmed the transaction. ```bash theme={null} npx msig getConfirmations [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Transaction id ### Get Confirmation Count Returns number of confirmations of a transaction (i.e how many times the transaction has been confirmed). ```bash theme={null} npx msig getConfirmationCount [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `` - Transaction id ### Is Transaction Confirmed Returns the confirmation status of transactions. If transaction ID was provided, than execution will return only status for that transaction. ```bash theme={null} npx msig isConfirmed [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Optional arguments: * `[transactionId]` - Transaction id ### Get Owners Returns list of owners on MultisigWallet on SKALE Chain. ```bash theme={null} npx msig getOwners [options] ``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) ### Get Balance of MultisigWallet Returns the sFUEL balance of address. ```bash theme={null} npx msig getBalance [options]
``` Required variables: * `ENDPOINT` - Endpoint of the SKALE chain * `PRIVATE_KEY_1` - Originatior private key (owner of the MultiSigWallet) Required arguments: * `
` - The address of which to return the sFUEL balance # Using SAFE Source: https://docs.skale.space/developers/run-a-skale-chain/using-safe Guidance on using SAFE to manage a SKALE Chain The Ethereum-based SAFE can be used to manage a SKALE Chain, IF set as the SKALE Chain owner during creation. It operates using SKALE's Native IMA bridge to execute on a SKALE Chain. ## Sending Transactions on SKALE Chain Sending transactions to your SKALE Chain requires encoding the function data to execute via both Marionette and IMA. The recommended way for this is to [use the multisigwallet CLI](/developers/run-a-skale-chain/using-multisig-wallet-cli) from SKALE. ### Prepare Transaction Data > This uses the [encodeData](/developers/run-a-skale-chain/using-multisig-wallet-cli#encode-function-data) command from MultisigWallet CLI The following example will help you grant a specific role to an externally owned account (EOA) on your SKALE Chain. This can be useful in the early days of setting up your chain to help distribute sFUEL with scripts as needed. | Name | Value | | ------------------- | ------------------------------------------------------------------ | | Contract Name | Etherbase | | Role | ETHER\_MANAGER\_ROLE | | Role Id (keccak256) | 0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046 | | Receiver | 0xa9E1712086412A157A2A187A111bFAb203C73F6E | | SKALE Chain Name | juicy-low-small-testnet | ```shell theme={null} npx msig encodeData juicy-low-small-testnet Etherbase grantRole 0xe0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046 0xa9E1712086412A157A2A187A111bFAb203C73F6E ``` ```shell theme={null} # Output 0x9448920200000561bf78bd39c8e04acfad7995005495094c7d395d405c3288f009efc402000000000000000000000000d2c0deface000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000d2ba3e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000442f2ff15de0ba7b49edc651b7ad93b374c67f1e9a0d37370168bbb86b81c569ebfa15f046000000000000000000000000a9e1712086412a157a2a187a111bfab203c73f6e00000000000000000000000000000000000000000000000000000000 ``` ### Submit Transaction to SAFE 1. Go to [SAFE App](https://safe.global) or your preferred frontend for SAFE. 2. Press **New Transaction** and then **Transaction Builder** Click on Transaction Builder 3. Enable the toggle **Custom Data** Enable the custom data toggle 4. Input the MessageProxy address for your correct environment into the **Contract Address** field and select **Use Implementation ABI** **MessageProxy Contract Addresses** | Network | Contract Address | | ------------------------ | ------------------------------------------ | | Ethereum Mainnet | 0x8629703a9903515818C2FeB45a6f6fA5df8Da404 | | Ethereum Holesky Testnet | 0x682ef859e1cE314ceD13A6FA32cE77AaeCE98e28 | 5. Input **0** for ETH Value 6. Input the above **Output** from the `npx msig...` command into the **Data (Hex Encoded)** field (see image). Transaction Prepared in SAFE 7. Click **Add transaction**, scroll down, and click **Create Batch**, then click **Send Batch**. [Tenderly](https://tenderly.co/) simulation is generally available, even on testnet, and is encouraged to be used to see all the changes occuring on the Ethereum side before they occur. ### Executing the Transaction Executing the transaction is equivalent to sending a transaction to the blockchain. As this requires the SKALE Network validators to pickup the message from Ethereum and post it to the SKALE Chain, please allow time for the Ethereum transaction to be included and for the message to post to SKALE. # Bridge ERC-1155 Tokens Source: https://docs.skale.space/developers/skale-bridge/ethereum/bridge-erc1155 Guide on bridging ERC-1155 tokens on SKALE SKALE's Interchain Messaging Agent includes a native bridging layer for ERC-1155 tokens, the most popular semi-fungible token standard that exists in blockchain today. The following introduces key information on setting up the bridgeable assets as well as how to programatically bridge the tokens. ## Important Information * When a SKALE Chain is created, there are NO ERC-1155 tokens mapped by default * In order to bridge an ERC-1155 token to a SKALE Chain from Ethereum, it must be **added** by the SKALE Chain owner or operator via the standard mapping process * All ERC-1155 tokens bridged into SKALE have a set of basic requirements to make them compatible with SKALE's bridging layer SKALE utilizes a popular architectural design within bridging which is a liquidity based bridge. This means that tokens *bridged* into SKALE are locked on Ethereum and a copy is minted on target SKALE Chain. ## Bridge Setup ### 1. Prepare the ERC-1155 on SKALE ERC-1155 tokens that are being bridged to SKALE should follow two basic requirements in order to be compatible with the SKALE IMA bridging layer: 1. Have a **mint** function that is locked down to the IMA Bridge 2. Have a **burn** function that is locked down to the IMA Bridge If the mint function is not locked down to just the IMA Bridge, specifically TokenManagerERC1155; there is a chance that the existing tokens on the SKALE Chain may not match what is secured in the bridge. If the burn function is not locked down to just the IMA Bridge, specifically TokenManagerERC1155; there is a chance that the supply on the SKALE Chain side could be "burned", while the tokens still exist on Ethereum which could later be withdrawn under certain circumstances. It is recommended that explicit burns on a SKALE Chain should be avoided for bridged tokens. The following is the base interchain token code that meets the above bridging requirements for IMA. It is not recommended to use this directly, but to use a wrapper. See InGameTokens for an example of InterchainERC1155 in use! ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.9; import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155.sol"; import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; contract InterchainERC1155 is AccessControl, ERC1155 { using Strings for uint256; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); string private _baseUri; constructor(string memory baseUri) ERC1155("") { _grantRole(MINTER_ROLE, 0xD2aaA00900000000000000000000000000000000); _baseUri = baseUri; // e.g. "ipfs://QmSomeCID/" } function uri(uint256 tokenId) public view override returns (string memory) { return string(abi.encodePacked(_baseUri, tokenId.toString(), ".json")); } function mint( address account, uint256 id, uint256 amount, bytes memory data ) external { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _mint(account, id, amount, data); } function mintBatch( address account, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) external { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _mintBatch(account, ids, amounts, data); } function setBaseUri(string memory newBaseUri) external onlyRole(DEFAULT_ADMIN_ROLE) { _baseUri = newBaseUri; } } ``` ### 2. Deploy the ERC-1155 on SKALE Utilize your preferred tooling i.e [Foundry](https://getfoundry.sh), [Hardhat](https://hardhat.org), [Remix](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=paris\&version=soljson-v0.8.19+commit.7dd6d404.js), etc. to deploy your IMA compatible ERC-1155 token to the SKALE Chain you want to be able to bridge assets too. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC1155 } from "./InterchainERC1155.sol"; contract InGameTokens is InterchainERC1155("ipfs://") {} ``` > *InterchainERC1155.sol is inherited from the code above* ### 3. Map the SKALE and Ethereum tokens together If you are trying to setup a token on SKALE in a production environment please join us in [Discord](https://discord.gg/skale) for support! #### Add the token on Ethereum via SAFE Start by visiting your SAFE via the official SAFE website or your preferred tool. If you are unsure, you can follow the steps [here](/developers/run-a-skale-chain/using-safe). Once on your SAFE, start by preparing a transaction via the **Transaction Builder**. Follow these steps: 1. Input the DepositBoxERC1155 address into the address field. 2. Select **Use Implementation ABI** 3. Select `addERC1155TokenByOwner` for the method 4. Input your *SKALE Chain Name* and your ERC-1155 contract address on Ethereum in the fields 5. Send by itself or via batch Make sure to replace the values with your SKALE Chain name and the correct token addresses. #### Add the token on SKALE Chain TokenManagerERC1155 ## Multisigwallet CLI ```shell theme={null} npx msig encodeData [SKALE_CHAIN_NAME] TokenManagerERC1155 addERC1155TokenByOwner Mainnet 0x[TOKEN_ON_ETHEREUM] 0x[TOKEN_ON_SKALE_CHAIN] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) #### Verify the Mapping To verify the mapping, you should have an event emitted from the following: 1. DepositBoxERC1155 on Ethereum - `event ERC1155TokenAdded(string schainName, address indexed contractOnMainnet);` 2. TokenManagerERC1155 on SKALE Chain - `event ERC1155TokenAdded(SchainHash indexed chainHash, address indexed erc1155OnMainChain, address indexed erc1155nSchain);` ## Bridging ERC-1155 The following does not require you to setup your own token. This works with **ANY** ERC-1155 token that is mapped from Ethereum to any SKALE Chain as long as the actual ERC-1155 token on each side does not have additional restrictions around who can transfer. The flow for bridging an ERC-1155 from Ethereum to SKALE follows a very similar flow to a standard ERC-1155 transfer: 1. Approve the bridge contract on the ERC-1155 token to allow it to move your NFTs 2. Call the bridge directly to transfer the specific ERC-1155 from Ethereum -> SKALE Chain, if the mapping exists 3. Wait for the message to be posted by the validator set on the SKALE Chain, which is the net-new minted NFT corresponding to the NFT (by id(s) and amount(s)) locked on Ethereum during the bridge ### Bridge to SKALE (from Ethereum) The following will help you bridge an NFT from Ethereum to SKALE. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC1155_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const DEPOSIT_BOX_ERC1155_ADDRESS = "[DEPOSIT_BOX_ERC1155_ADDRESS]"; const DEPOSIT_BOX_ERC1155_ABI = [ "function depositERC1155(string calldata schainName, address erc1155OnMainnet, uint256 id, uint256 amount) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const ONE_TOKEN = parseEther("1"); // 100 tokens in wei format const TOKEN_ID = BigInt(1); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC1155_ADDRESS, DEPOSIT_BOX_ERC1155_ABI, wallet); const erc1155TokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ALL ERC-1155 tokens (this is required by the standard) const approvalTx = await erc1155TokenContract.setApprovalForAll(DEPOSIT_BOX_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-20 into bridge, will receive on same address on SKALE const depositTx = await depositBoxContract.depositERC1155(SKALE_CHAIN_NAME, ERC1155_ADDRESS, TOKEN_ID, ONE_TOKEN); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC1155_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const DEPOSIT_BOX_ERC1155_ADDRESS = "[DEPOSIT_BOX_ERC1155_ADDRESS]"; const DEPOSIT_BOX_ERC1155_ABI = [ "function depositERC1155Direct(string calldata schainName, address erc1155OnMainnet, uint256 id, uint256 amount, address receiver) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const ONE_TOKEN = parseEther("1"); // 100 tokens in wei format const TOKEN_ID = BigInt(1); const CUSTOM_RECEIVER = "[CUSTOM_RECEIVER_ADDRESS]"; // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC1155_ADDRESS, DEPOSIT_BOX_ERC1155_ABI, wallet); const erc1155TokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ALL ERC-1155 tokens (this is required by the standard) const approvalTx = await erc1155TokenContract.setApprovalForAll(DEPOSIT_BOX_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-20 into bridge, will receive on custom receiver address on SKALE const depositTx = await depositBoxContract.depositERC1155Direct(SKALE_CHAIN_NAME, ERC1155_ADDRESS, TOKEN_ID, ONE_TOKEN, CUSTOM_RECEIVER); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC1155_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const DEPOSIT_BOX_ERC1155_ADDRESS = "[DEPOSIT_BOX_ERC1155_ADDRESS]"; const DEPOSIT_BOX_ERC1155_ABI = [ "function depositERC1155Batch(string calldata schainName, address erc1155OnMainnet, uint256[] calldata ids, uint256[] calldata amounts) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const TOKEN_AMOUNTS = [parseEther("1"), parseEther("2")] // 1 and 2 tokens in wei format const TOKEN_IDS = [BigInt(1), BigInt(2)] // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC1155_ADDRESS, DEPOSIT_BOX_ERC1155_ABI, wallet); const erc1155TokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ALL ERC-1155 tokens (this is required by the standard) const approvalTx = await erc1155TokenContract.setApprovalForAll(DEPOSIT_BOX_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-20 into bridge, will receive on same address on SKALE const depositTx = await depositBoxContract.depositERC1155Batch(SKALE_CHAIN_NAME, ERC1155_ADDRESS, TOKEN_IDS, TOKEN_AMOUNTS); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC1155_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const DEPOSIT_BOX_ERC1155_ADDRESS = "[DEPOSIT_BOX_ERC1155_ADDRESS]"; const DEPOSIT_BOX_ERC1155_ABI = [ "function depositERC1155BatchDirect(string calldata schainName, address erc1155OnMainnet, uint256[] calldata ids, uint256[] calldata amounts, address receiver) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const TOKEN_AMOUNTS = [parseEther("1"), parseEther("2")] // 1 and 2 tokens in wei format const TOKEN_IDS = [BigInt(1), BigInt(2)] const CUSTOM_RECEIVER = "[CUSTOM_RECEIVER_ADDRESS]"; // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC1155_ADDRESS, DEPOSIT_BOX_ERC1155_ABI, wallet); const erc1155TokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ALL ERC-1155 tokens (this is required by the standard) const approvalTx = await erc1155TokenContract.setApprovalForAll(DEPOSIT_BOX_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-20 into bridge, will receive on custom receiver address on SKALE const depositTx = await depositBoxContract.depositERC1155BatchDirect(SKALE_CHAIN_NAME, ERC1155_ADDRESS, TOKEN_IDS, TOKEN_AMOUNTS, CUSTOM_RECEIVER); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ### Bridge to Ethereum (from SKALE) SKALE's decentralized bridge offers a simple two-step process to bridge from any SKALE Chain to Ethereum Mainnet. 1. The first step, which only has to be done if you don't have a sufficient balance to exit, is to fill up your gas wallet on Ethereum 2. The second step is to initiate the bridge (technically known as an exit) on the SKALE Chain Gas Wallet, officially referred to as community pool, is a smart contract on Ethereum that is used to handle exits from SKALE. Users pre-pay ETH into this contract which is then used to reimburse validators for the gas costs of the bridge cost back to Ethereum. Make sure users top up their gas wallet to ensure the bridge can succeed. #### Pre-pay for your Exit This step is optional IF the user has already filled up their gas wallet and has sufficient balance left. You can check if the wallet is an `activeUser` on the CommunityLocker 0xD2aaa00300000000000000000000000000000000 smart contract on the SKALE Chain. If active, no need to fill the pool again. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const COMMUNITY_POOL_ADDRESS = "[COMMUNIY_POOL_ADDRESS]"; const COMMUNITY_POOL_ABI = [ "function rechargeUserWallet(string calldata schainName, address user) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const communityPoolContract = new Contract(COMMUNITY_POOL_ADDRESS, COMMUNITY_POOL_ABI, wallet); const rechargeTx = await communityPoolContract.rechargeUserWallet( SKALE_CHAIN_NAME, wallet.address, { value: parseEther("0.02") // Recharge by 0.02 ETH } ); await rechargeTx.wait(5); // wait 5 blocks for full finality // Success! You can now bridge from SKALE to Ethereum! console.log("Success!"); ``` #### Bridge to Ethereum Once the above prepayment steps are completed, you can proceed with the bridging. Bridging from SKALE simply requires the `exitToMainERC1155` function to be called with the corresponding token and amount to initiate the transfer back to Ethereum. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const SKALE_RPC_URL = "[YOUR_SKALE_RPC_URL]"; const ERC1155_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const TOKEN_MANAGER_ERC1155_ADDRESS = "0xD2aaA00900000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC1155_ABI = [ "function exitToMainERC1155(address contractOnMainnet, uint256 id, uint256 amount) external" ]; const TOKEN_ID = BigInt(1); // Token Id #1 const AMOUNT = parseEther("2"); // 2 in wei form // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(SKALE_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerERC1155Contract = new Contract(TOKEN_MANAGER_ERC1155_ADDRESS, TOKEN_MANAGER_ERC1155_ABI, wallet); const erc1155TokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ERC-1155 on your behalf const approvalTx = await erc1155TokenContract.setApprovalForAll(TOKEN_MANAGER_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Transfer ERC-1155 Token into bridge, will recieve on the same address on Ethereum const exitTx = await tokenManagerERC1155Contract.exitToMainERC1155(ERC1155_ADDRESS, TOKEN_ID, AMOUNT); await exitTx.wait(1); // Success! Now watch for delivery on Ethereum console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const SKALE_RPC_URL = "[YOUR_SKALE_RPC_URL]"; const ERC1155_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const TOKEN_MANAGER_ERC1155_ADDRESS = "0xD2aaA00900000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC1155_ABI = [ "function exitToMainERC1155Batch(address contractOnMainnet, uint256[] calldata ids, uint256[] calldata amounts) external" ]; const TOKEN_IDS = [BigInt(1), BigInt(2)]; // Token Id #1, #2 const AMOUNTS = [parseEther("5"), parseEther("1")]; // 5 and 1 in wei // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(SKALE_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerERC1155Contract = new Contract(TOKEN_MANAGER_ERC1155_ADDRESS, TOKEN_MANAGER_ERC1155_ABI, wallet); const erc1155TokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ERC-1155 on your behalf const approvalTx = await erc1155TokenContract.setApprovalForAll(TOKEN_MANAGER_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Transfer ERC-1155 Tokens into bridge, will recieve on the same address on Ethereum const exitTx = await tokenManagerERC1155Contract.exitToMainERC1155Batch(ERC1155_ADDRESS, TOKEN_IDS, AMOUNTS); await exitTx.wait(1); // Success! Now watch for delivery on Ethereum console.log("Success!"); ``` If your bridge is not working, it is most likely for one of three reasons: 1. You forgot to prepay for gas 2. You forgot to approve bridge on the ERC-1155 token id you want to bridge 3. You don't actually own the ERC-1155 Token Still having issues? Join us in [Discord](https://discord.gg/skale) for support! # Bridge ERC-20 Tokens Source: https://docs.skale.space/developers/skale-bridge/ethereum/bridge-erc20 Guide on bridging ERC-20 tokens on SKALE SKALE's Interchain Messaging Agent includes a native bridging layer for ERC-20 tokens, the most popular fungible token standard that exists in blockchain today. The following introduces key information on setting up the bridgeable token as well as how to actually programatically bridge the token. ## Important Information * When a SKALE Chain is created, there are NO ERC-20 tokens mapped by default * In order to bridge an ERC-20 token to a SKALE Chain from Ethereum, it must be **added** by the SKALE Chain owner or operator via the standard mapping process * All ERC-20 tokens bridged into SKALE have a set of basic requirements to make them compatible with SKALE's bridging layer SKALE utilizes a popular architectural design within bridging which is a liquidity based bridge. This means that tokens *bridged* into SKALE are locked on Ethereum and a copy is minted on the target SKALE Chain. ## Bridge Setup ### 1. Prepare the ERC-20 on SKALE ERC-20 tokens that are being bridged to SKALE should follow two basic requirements in order to be compatible with the SKALE IMA bridging layer: 1. Have a **mint** function that is locked down to the IMA Bridge 2. Have a **burn** function that is locked down to the IMA Bridge If the mint function is not locked down to just the IMA Bridge, specifically TokenManagerERC20; there is a chance that the supply on the SKALE Chain side could be larger than what is actually deposited in the bridge. If the burn function is not locked down to just the IMA Bridge, specifically TokenManagerERC20; there is a chance that the supply on the SKALE Chain side could be "burned", while the tokens still exist on Ethereum which could later be withdrawn under certain circumstances. It is recommended that explicit burns on a SKALE Chain should be avoided for bridged tokens. The following is the base interchain token code that meets the above bridging requirements for IMA. It is not recommended to use this directly, but to use a wrapper. See InterchainSKL for an 18 decimal example and InterchainUSDT for a 6 decimal example. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; // Importing the ERC20 standard contract and AccessControl for role-based access management. import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title InterchainERC20 * @dev This contract is an ERC20 token implementation with role-based access control for minting and burning. * It utilizes OpenZeppelin's ERC20 and AccessControl for functionality. */ contract InterchainERC20 is ERC20, AccessControl { // Define roles using hashed constants for efficient comparison. bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); constructor(string memory name, string memory symbol) ERC20(name, symbol) { // Assign the minter role to a predefined address. _grantRole(MINTER_ROLE, 0xD2aAA00500000000000000000000000000000000); // Assign the burner role to a predefined address. _grantRole(BURNER_ROLE, 0xD2aAA00500000000000000000000000000000000); } function mint(address to, uint256 amount) public virtual { // Ensure that the caller has the MINTER_ROLE. require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); // Mint the specified amount of tokens to the target address. _mint(to, amount); } function burn(uint256 amount) public virtual { // Ensure that the caller has the BURNER_ROLE. require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner"); // Burn the specified amount of tokens from the caller's account. _burn(msg.sender, amount); } } ``` ### 2. Deploy the ERC-20 on SKALE Utilize your preferred tooling i.e [Foundry](https://getfoundry.sh), [Hardhat](https://hardhat.org), [Remix](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=paris\&version=soljson-v0.8.19+commit.7dd6d404.js), etc. to deploy your IMA compatible ERC-20 token to the SKALE Chain you want to be able to bridge assets too. *This is an example of an 18 decimal ERC-20 token that would be deployed on SKALE* ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC20 } from "./InterchainERC20.sol"; contract InterchainSKL is InterchainERC20("Skale Token", "SKL") {} ``` *This is an example of an 6 decimal ERC-20 token that would be deployed on SKALE* ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC20 } from "./InterchainERC20.sol"; contract InterchainUSDT is InterchainERC20("Tether USD", "USDT") { function decimals() public view override returns (uint8) { return 6; } } ``` > *InterchainERC20.sol is inherited from the code above* ### 3. Map the SKALE and Ethereum tokens together If you are trying to setup a token on SKALE in a production environment please join us in [Discord](https://discord.gg/skale) for support! #### Add the token on Ethereum via SAFE Start by visiting your SAFE via the official SAFE website or your preferred tool. If you are unsure, you can follow the steps [here](/developers/run-a-skale-chain/using-safe). Once on your SAFE, start by preparing a transaction via the **Transaction Builder** and follow the screenshots below, which will use Europa Testnet and the SKL token as en example for the mapping. Make sure to replace the values with your SKALE Chain name and the correct token addresses. #### Add the token on SKALE Chain TokenManagerERC20 ```shell theme={null} npx msig encodeData [SKALE_CHAIN_NAME] TokenManagerERC20 addERC20TokenByOwner Mainnet 0x[TOKEN_ON_ETHEREUM] 0x[TOKEN_ON_SKALE_CHAIN] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) #### Verify the Mapping To verify the mapping, you should have an event emitted from the following: 1. DepositBoxERC20 on Ethereum - `event ERC20TokenAdded(string schainName, address indexed contractOnMainnet);` 2. TokenManagerERC20 on SKALE Chain - `event ERC20TokenAdded(SchainHash indexed chainHash, address indexed erc20OnMainChain, address indexed erc20OnSchain);` ## Bridging ERC-20 The following does not require you to setup your own token. This works with **ANY** ERC-20 token that is mapped from Ethereum to any SKALE Chain as long as the actual ERC-20 token on each side does not have additional restrictions around who can transfer. The flow for bridging an ERC-20 from Ethereum to SKALE follows a very similar flow to a standard ERC-20 transfer: 1. Approve the bridge contract on the ERC-20 token to allow it to control some amount of funds 2. Call the bridge directly to transfer the asset from Ethereum -> SKALE Chain, if the mapping exists 3. Wait for the message to be posted by the validator set on the SKALE Chain, which is the net-new minted tokens corresponding to the value locked on Ethereum during the bridge ### Bridge to SKALE (from Ethereum) A normal ERC-20 deposit will result in tokens being delivered to the same wallet on SKALE. A direct ERC-20 deposit will result in tokens being delivered to the specified wallet on SKALE. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC20_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC20_ABI = [ "function approve(address spender, uint256 amount) external" ]; const DEPOSIT_BOX_ERC20_ADDRESS = "[DEPOSIT_BOX_ERC20_ADDRESS]"; const DEPOSIT_BOX_ERC20_ABI = [ "function depositERC20(string calldata schainName, address erc20OnMainnet, uint256 amount) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const ONE_HUNDRED_TOKENS = parseEther("100"); // 100 tokens in wei format // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC20_ADDRESS, DEPOSIT_BOX_ERC20_ABI, wallet); const erc20TokenContract = new Contract(ERC20_ADDRESS, ERC20_ABI, wallet); // 1. Approve the bridged to move ERC-20 const approvalTx = await erc20TokenContract.approve(DEPOSIT_BOX_ERC20_ADDRESS, ONE_HUNDRED_TOKENS); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-20 into bridge, will receive on same address on SKALE const depositTx = await depositBoxContract.depositERC20(SKALE_CHAIN_NAME, ERC20_ADDRESS, ONE_HUNDRED_TOKENS); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC20_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC20_ABI = [ "function approve(address spender, uint256 amount) external" ]; const DEPOSIT_BOX_ERC20_ADDRESS = "[DEPOSIT_BOX_ERC20_ADDRESS]"; const DEPOSIT_BOX_ERC20_ABI = [ "function depositERC20Direct(string calldata schainName, address erc20OnMainnet, uint256 amount, address receiver) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const ONE_HUNDRED_TOKENS = parseEther("100"); // 100 tokens in wei format const CUSTOM_RECEIVER = "[CUSTOM_RECEIVER_WALLET_ADDRESS]"; // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC20_ADDRESS, DEPOSIT_BOX_ERC20_ABI, wallet); const erc20TokenContract = new Contract(ERC20_ADDRESS, ERC20_ABI, wallet); // 1. Approve the bridged to move ERC-20 const approvalTx = await erc20TokenContract.approve(DEPOSIT_BOX_ERC20_ADDRESS, ONE_HUNDRED_TOKENS); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-20 into bridge, will receive on the CUSTOM_RECEIVER address on SKALE const depositTx = await depositBoxContract.depositERC20Direct(SKALE_CHAIN_NAME, ERC20_ADDRESS, ONE_HUNDRED_TOKENS, CUSTOM_RECEIVER); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ### Bridge to Ethereum (from SKALE) SKALE's decentralized bridge offers a simple two-step process to bridge from any SKALE Chain to Ethereum Mainnet. 1. The first step, which only has to be done if you don't have a sufficient balance to exit, is to fill up your gas wallet on Ethereum 2. The second step is to initiate the bridge (technically known as an exit) on the SKALE Chain Gas Wallet, officially referred to as the community pool, is a smart contract on Ethereum that is used to handle exits from SKALE. Users pre-pay ETH into this contract which is then used to reimburse validators for the gas costs of the bridge cost back to Ethereum. Make sure users top up their gas wallet to ensure bridge can succeed. #### Pre-pay for your Exit This step is optional IF the user has already filled up their gas wallet and has sufficient balance left. You can check if the wallet is an `activeUser` on the CommunityLocker 0xD2aaa00300000000000000000000000000000000 smart contract on the SKALE Chain. If active, no need to fill the pool again. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const COMMUNITY_POOL_ADDRESS = "[COMMUNIY_POOL_ADDRESS]"; const COMMUNITY_POOL_ABI = [ "function rechargeUserWallet(string calldata schainName, address user) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const communityPoolContract = new Contract(COMMUNITY_POOL_ADDRESS, COMMUNITY_POOL_ABI, wallet); const rechargeTx = await communityPoolContract.rechargeUserWallet( SKALE_CHAIN_NAME, wallet.address, { value: parseEther("0.02") // Recharge by 0.02 ETH } ); await rechargeTx.wait(5); // wait 5 blocks for full finality // Success! You can now bridge from SKALE to Ethereum! console.log("Success!"); ``` #### Bridge to Ethereum Once the above prepayment steps are completed, you can proceed with the bridging. Bridging from SKALE simply requires the `exitToMainERC20` function to be called with the corresponding token and amount to initiate the transfer back to Ethereum. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const SKALE_RPC_URL = "[YOUR_SKALE_RPC_URL]"; const ERC20_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC20_ABI = [ "function approve(address spender, uint256 amount) external" ]; const TOKEN_MANAGER_ERC20_ADDRESS = "0xD2aAA00500000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC20_ABI = [ "function exitToMainERC20(address contractOnMainnet, uint256 amount) external" ]; const ONE_HUNDRED_TOKENS = parseEther("100"); // 100 tokens in wei format // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(SKALE_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerERC20Contract = new Contract(TOKEN_MANAGER_ERC20_ADDRESS, TOKEN_MANAGER_ERC20_ABI, wallet); const erc20TokenContract = new Contract(ERC20_ADDRESS, ERC20_ABI, wallet); // 1. Approve the bridge to move ERC-20 on your behalf const approvalTx = await erc20TokenContract.approve(TOKEN_MANAGER_ERC20_ADDRESS, ONE_HUNDRED_TOKENS); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Transfer ERC-20 into bridge, will recieve on the same address on Ethereum const exitTx = await tokenManagerERC20Contract.exitToMainERC20(ERC20_ADDRESS, ONE_HUNDRED_TOKENS); await exitTx.wait(1); // Success! Now watch for delivery on Ethereum console.log("Success!"); ``` If your bridge is not working, it is most likely for one of three reasons: 1. You forgot to prepay for gas 2. You forgot to approve the bridge on the ERC-20 token 3. You don't have sufficient funds to bridge Still having issues? Join us in [Discord](https://discord.gg/skale) for support! # Bridge ERC-721 Tokens Source: https://docs.skale.space/developers/skale-bridge/ethereum/bridge-erc721 Guide on bridging ERC-721 tokens on SKALE SKALE's Interchain Messaging Agent includes a native bridging layer for ERC-721 tokens, the most popular non-fungible token standard that exists in blockchain today. The following introduces key information on setting up the bridgeable collectibles as well as how to actually programatically bridge the tokens. ## Important Information * When a SKALE Chain is created, there are NO ERC-721 tokens mapped by default * In order to bridge an ERC-721 token to a SKALE Chain from Ethereum, it must be **added** by the SKALE Chain owner or operator via the standard mapping process * All ERC-721 tokens bridged into SKALE have a set of basic requirements to make them compatible with SKALE's bridging layer SKALE utilizes a popular architectural design within bridging which is a liquidity based bridge. This means that tokens *bridged* into SKALE are locked on Ethereum and a copy is minted on the target SKALE Chain. ## Bridge Setup ### 1. Prepare the ERC-721 on SKALE ERC-721 tokens that are being bridged to SKALE should follow two basic requirements in order to be compatible with the SKALE IMA bridging layer: 1. Have a **mint** function that is locked down to the IMA Bridge 2. Have a **burn** function that is locked down to the IMA Bridge If the mint function is not locked down to just the IMA Bridge, specifically TokenManagerERC721; there is a chance that the existing tokens on the SKALE Chain may not match what is secured in the bridge. If the burn function is not locked down to just the IMA Bridge, specifically TokenManagerERC721; there is a chance that the supply on the SKALE Chain side could be "burned", while the tokens still exist on Ethereum which could later be withdrawn under certain circumstances. It is recommended that explicit burns on a SKALE Chain should be avoided for bridged tokens. The following is the base interchain token code that meets the above bridging requirements for IMA. It is not recommended to use this directly, but to use a wrapper. See InterchainSKL for an 18 decimal example and InterchainUSDT for a 6 decimal example. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; /** * @title InterchainERC721 * @dev ERC-721 with role-based minting and dynamic token URI. */ contract InterchainERC721 is AccessControl, ERC721 { using Strings for uint256; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); string private _baseTokenURI; constructor( string memory contractName, string memory contractSymbol, string memory baseTokenURI ) ERC721(contractName, contractSymbol) { _setRoleAdmin(MINTER_ROLE, 0xD2aaa00600000000000000000000000000000000); // example admin role _grantRole(MINTER_ROLE, _msgSender()); _baseTokenURI = baseTokenURI; } function mint(address to, uint256 tokenId) external returns (bool) { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _safeMint(to, tokenId); return true; } /// @notice Override to return dynamic token URI function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721: URI query for nonexistent token"); return string(abi.encodePacked(_baseTokenURI, tokenId.toString(), ".json")); } /// @notice Allows admin to update base URI function setBaseURI(string calldata newBaseURI) external onlyRole(MINTER_ROLE) { _baseTokenURI = newBaseURI; } } ``` ### 2. Deploy the ERC-721 on SKALE Utilize your preferred tooling i.e [Foundry](https://getfoundry.sh), [Hardhat](https://hardhat.org), [Remix](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=paris\&version=soljson-v0.8.19+commit.7dd6d404.js), etc. to deploy your IMA compatible ERC-721 token to the SKALE Chain you want to be able to bridge assets too. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC721 } from "./InterchainERC721.sol"; contract InterchainNFT is InterchainERC721("NFT Name", "NFT Symbol", "ipfs:///") {} ``` > *InterchainERC721.sol is inherited from the code above* ### 3. Map the SKALE and Ethereum tokens together If you are trying to setup a token on SKALE in a production environment please join us in [Discord](https://discord.gg/skale) for support! #### Add the token on Ethereum via SAFE Start by visiting your SAFE via the official SAFE website or your preferred tool. If you are unsure, you can follow the steps [here](/developers/run-a-skale-chain/using-safe). Once on your SAFE, start by preparing a transaction via the **Transaction Builder**. Follow these steps: 1. Input the DepositBoxERC721 address into the address field. 2. Select **Use Implementation ABI** 3. Select `addERC721TokenByOwner` for the method 4. Input your *SKALE Chain Name* and your ERC-721 contract address on Ethereum in the fields 5. Send by itslef or via batch Make sure to replace the values with your SKALE Chain name and the correct token addresses. #### Add the token on SKALE Chain TokenManagerERC721 ```shell theme={null} npx msig encodeData [SKALE_CHAIN_NAME] TokenManagerERC721 addERC721TokenByOwner Mainnet 0x[TOKEN_ON_ETHEREUM] 0x[TOKEN_ON_SKALE_CHAIN] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) #### Verify the Mapping To verify the mapping, you should have an event emitted from the following: 1. DepositBoxERC721 on Ethereum - `event ERC721TokenAdded(string schainName, address indexed contractOnMainnet);` 2. TokenManagerERC721 on SKALE Chain - `event ERC721TokenAdded(SchainHash indexed chainHash, address indexed erc721OnMainChain, address indexed erc721OnSchain);` ## Bridging ERC-721 The following does not require you to setup your own token. This works with **ANY** ERC-721 token that is mapped from Ethereum to any SKALE Chain as long as the actual ERC-721 token on each side does not have additional restrictions around who can transfer. The flow for bridging an ERC-721 from Ethereum to SKALE follows a very similar flow to a standard ERC-721 transfer: 1. Approve the bridge contract on the ERC-721 token to allow it to move your NFTs 2. Call the bridge directly to transfer the specific ERC-721 from Ethereum -> SKALE Chain, if the mapping exists 3. Wait for the message to be posted by the validator set on the SKALE Chain, which is the net-new minted NFT corresponding to the NFT (by id) locked on Ethereum during the bridge ### Bridge to SKALE (from Ethereum) The following will help you bridge an NFT from Ethereum to SKALE. ```js theme={null} import { Contract, JsonRpcProvider, Wallet } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC721_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC721_ABI = [ "function approve(address to, uint256 tokenId) external" ]; const DEPOSIT_BOX_ERC721_ADDRESS = "[DEPOSIT_BOX_ERC20_ADDRESS]"; const DEPOSIT_BOX_ERC721_ABI = [ "function depositERC721(string calldata schainName, address erc721OnMainnet, uint256 tokenId) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const TOKEN_ID = BigInt(1); // TokenId to bridge // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC721_ADDRESS, DEPOSIT_BOX_ERC721_ABI, wallet); const erc721TokenContract = new Contract(ERC721_ADDRESS, ERC721_ABI, wallet); // 1. Approve the bridge to move ERC-721 Token Id #1 const approvalTx = await erc721TokenContract.approve(DEPOSIT_BOX_ERC721_ADDRESS, TOKEN_ID); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-721 Token Id #1 into bridge, will receive on same address on SKALE const depositTx = await depositBoxContract.depositERC721(SKALE_CHAIN_NAME, ERC721_ADDRESS, TOKEN_ID); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const ERC721_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC721_ABI = [ "function approve(address to, uint256 tokenId) external" ]; const DEPOSIT_BOX_ERC721_ADDRESS = "[DEPOSIT_BOX_ERC20_ADDRESS]"; const DEPOSIT_BOX_ERC721_ABI = [ "function depositERC721Direct(string calldata schainName, address erc721OnMainnet, uint256 tokenId, address receiver) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const TOKEN_ID = BigInt(1); // TokenId to bridge const CUSTOM_RECEIVER = "[CUSTOM_RECEIVER_WALLET_ADDRESS]"; // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const depositBoxContract = new Contract(DEPOSIT_BOX_ERC721_ADDRESS, DEPOSIT_BOX_ERC721_ABI, wallet); const erc721TokenContract = new Contract(ERC721_ADDRESS, ERC721_ABI, wallet); // 1. Approve the bridge to move ERC-721 Token Id #1 const approvalTx = await erc721TokenContract.approve(DEPOSIT_BOX_ERC721_ADDRESS, TOKEN_ID); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~15 seconds // 2. Deposit ERC-721 Token Id #1 into bridge, will receive on custom address on SKALE const depositTx = await depositBoxContract.depositERC721Direct(SKALE_CHAIN_NAME, ERC721_ADDRESS, TOKEN_ID, CUSTOM_RECEIVER); await depositTx.wait(1); // Success! Now watch for delivery on SKALE Chain console.log("Success!"); ``` ### Bridge to Ethereum (from SKALE) SKALE's decentralized bridge offers a simple two-step process to bridge from any SKALE Chain to Ethereum Mainnet. 1. The first step, which only has to be done if you don't have a sufficient balance to exit, is to fill up your gas wallet on Ethereum 2. The second step is to initiate the bridge (technically known as an exit) on the SKALE Chain Gas Wallet, officially referred to as the community pool, is a smart contract on Ethereum that is used to handle exits from SKALE. Users pre-pay ETH into this contract which is then used to reimburse validators for the gas costs of the bridge cost back to Ethereum. Make sure users top up their gas wallet to ensure the bridge can succeed. #### Pre-pay for your Exit This step is optional IF the user has already filled up their gas wallet and has sufficient balance left. You can check if the wallet is an `activeUser` on the CommunityLocker 0xD2aaa00300000000000000000000000000000000 smart contract on the SKALE Chain. If active, no need to fill the pool again. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const COMMUNITY_POOL_ADDRESS = "[COMMUNIY_POOL_ADDRESS]"; const COMMUNITY_POOL_ABI = [ "function rechargeUserWallet(string calldata schainName, address user) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const communityPoolContract = new Contract(COMMUNITY_POOL_ADDRESS, COMMUNITY_POOL_ABI, wallet); const rechargeTx = await communityPoolContract.rechargeUserWallet( SKALE_CHAIN_NAME, wallet.address, { value: parseEther("0.02") // Recharge by 0.02 ETH } ); await rechargeTx.wait(5); // wait 5 blocks for full finality // Success! You can now bridge from SKALE to Ethereum! console.log("Success!"); ``` #### Bridge to Ethereum Once the above prepayment steps are completed, you can proceed with the bridging. Bridging from SKALE simply requires the `exitToMainERC721` function to be called with the corresponding token and amount to initiate the transfer back to Ethereum. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const SKALE_RPC_URL = "[YOUR_SKALE_RPC_URL]"; const ERC721_ADDRESS = "[YOUR_TOKEN_ADDRESS]"; const ERC721_ABI = [ "function approve(address spender, uint256 amount) external" ]; const TOKEN_MANAGER_ERC721_ADDRESS = "0xD2aaa00600000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC721_ABI = [ "function exitToMainERC721(address contractOnMainnet, uint256 tokenId) external" ]; const TOKEN_ID = BigInt(1); // Token Id #1 // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(SKALE_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerERC721Contract = new Contract(TOKEN_MANAGER_ERC721_ADDRESS, TOKEN_MANAGER_ERC721_ABI, wallet); const erc721TokenContract = new Contract(ERC721_ADDRESS, ERC721_ABI, wallet); // 1. Approve the bridge to move ERC-721 on your behalf const approvalTx = await erc721TokenContract.approve(TOKEN_MANAGER_ERC721_ADDRESS, TOKEN_ID); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Transfer ERC-721 Token into bridge, will recieve on the same address on Ethereum const exitTx = await tokenManagerERC721Contract.exitToMainERC721(ERC721_ADDRESS, TOKEN_ID); await exitTx.wait(1); // Success! Now watch for delivery on Ethereum console.log("Success!"); ``` If your bridge is not working, it is most likely for one of three reasons: 1. You forgot to prepay for gas 2. You forgot to approve the bridge on the ERC-721 token id you want to bridge 3. You don't actually own the ERC-721 Token Still having issues? Join us in [Discord](https://discord.gg/skale) for support! # Bridge ETH Source: https://docs.skale.space/developers/skale-bridge/ethereum/bridge-eth Guide on bridging ETH from Ethereum to a SKALE Chain SKALE's Interchain Messaging Agent includes a native bridging layer for industry standard assets including the native gas token of it's base layer, ETH on Ethereum. You can use IMA to easily send ETH to any SKALE Chain. Here are a few important notes to simplify the flow for your application: ## Important Information * Unlike ERC-20, ERC-721, and ERC-1155; ETH is nativley support between Ethereum and SKALE Chains. This means you do **NOT** need to map ETH to a token * ETH has its own special DepositBox on Ethereum called DepositBoxETH which holds ETH separately from the other assets bridged to the SKALE Network When ETH is deposited on Ethereum, it is received as an ERC-20 token on all SKALE Chains. This token is **NOT** WETH. This token is predeployed on all SKALE Chains to the same token address and information as [ETHC](#eth-on-skale). ### ETH on SKALE ETH when bridged to a SKALE Chain always "mints" to the same place. This is flagged as important to avoid confusion with you and your users. | Key | Value | Note | | ---------------- | ------------------------------------------ | -------------------------------------------------------- | | Name | ERC20 Ether Clone | Use ETH, Ether, or Ethereum on your frontend | | Symbol | ETHC | Use ETH on your frontend | | Contract Address | 0xD2Aaa00700000000000000000000000000000000 | This is the same on all SKALE Chains and will not change | An example of ETH on SKALE can be seen [here on Europa Mainnet block explorer](https://elated-tan-skat.explorer.mainnet.skalenodes.com/token/0xD2Aaa00700000000000000000000000000000000). ## Bridging ETH To bridge ETH from Ethereum to SKALE, you should deposit ETH on Ethereum Mainnet to DepositBoxEth, a smart contract that ### Bridge to SKALE (from Ethereum) The following does not require any gas (i.e sFUEL) on the SKALE Chain. Please note to **exit** to Ethereum once you are on SKALE, you will need ETH on Ethereum to fill up your community wallet. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const DEPOSIT_BOX_ETH_ADDRESS = "[DEPOSIT_BOX_ETH_ADDRESS]"; const DEPOSIT_BOX_ETH_ABI = [ "function deposit(string memory schainName) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); const wallet = new Wallet(PRIVATE_KEY, provider); const contract = new Contract(DEPOSIT_BOX_ETH_ADDRESS, DEPOSIT_BOX_ETH_ABI, wallet); const depositTransaction = await contract.deposit(SKALE_CHAIN_NAME, { value: parseEther("1") // set in wei -> 1000000000000000000 }); await depositTransaction.wait(2); // Wait 2 blocks for confirmation, you may choose anything >= 1 ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const DEPOSIT_BOX_ETH_ADDRESS = "[DEPOSIT_BOX_ETH_ADDRESS]"; const DEPOSIT_BOX_ETH_ABI = [ "function depositDirect(string memory schainName, address receiver) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); const wallet = new Wallet(PRIVATE_KEY, provider); const contract = new Contract(DEPOSIT_BOX_ETH_ADDRESS, DEPOSIT_BOX_ETH_ABI, wallet); const depositTransactionToDiffWallet = await contract.depositDirect(SKALE_CHAIN_NAME, "0x...", { value: parseEther("1") // set in wei -> 1000000000000000000 }); await depositTransactionToDiffWallet.wait(2); // Wait 2 blocks for confirmation, you may choose anything >= 1 ``` ### Bridge to Etheruem (from SKALE) SKALE's decentralized bridge offers a simple two-step process to bridge ETH from any SKALE Chain to Ethereum Mainnet. 1. The first step, which only has to be done if you don't have a sufficient balance to exit, is to fill up your gas wallet on Ethereum 2. The second step is to initiate the bridge (technically known as an exit) on the SKALE Chain Gas Wallet, officially referred to as the community pool, is a smart contract on Ethereum that is used to handle exits from SKALE. Users pre-pay ETH into this contract which is then used to reimburse validators for the gas costs of the bridge cost back to Ethereum. Make sure users top up their gas wallet to ensure the bridge is successful. #### Pre-pay for your Exit This step is optional IF the user has already filled up their gas wallet and has sufficient balance left. You can check if the wallet is an `activeUser` on the CommunityLocker 0xD2aaa00300000000000000000000000000000000 smart contract on the SKALE Chain. If active, no need to fill the pool again. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ETHEREUM_RPC_URL = "[YOUR_ETHEREUM_RPC_URL]"; const COMMUNITY_POOL_ADDRESS = "[COMMUNIY_POOL_ADDRESS]"; const COMMUNITY_POOL_ABI = [ "function rechargeUserWallet(string calldata schainName, address user) external" ]; const SKALE_CHAIN_NAME = "[SKALE_CHAIN_NAME]"; // e.g elated-tan-skat (europa mainnnet); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ETHEREUM_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const communityPoolContract = new Contract(COMMUNITY_POOL_ADDRESS, COMMUNITY_POOL_ABI, wallet); const rechargeTx = await communityPoolContract.rechargeUserWallet( SKALE_CHAIN_NAME, wallet.address, { value: parseEther("0.02") // Recharge by 0.02 ETH } ); await rechargeTx.wait(5); // wait 5 blocks for full finality // Success! You can now bridge from SKALE to Ethereum! console.log("Success!"); ``` #### Bridge to Ethereum Once the above prepayment steps are completed, you can proceed with the bridging. Bridging from SKALE simply requires the `exitToMain` function to be called with the corresponding token and amount to initiate the transfer back to Ethereum. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const SKALE_RPC_URL = "[YOUR_SKALE_RPC_URL]"; const ETH_ERC20_ADDRESS = "0xD2Aaa00700000000000000000000000000000000"; // DO NOT CHANGE THIS const ERC20_ABI = [ "function approve(address spender, uint256 amount) external" ]; const TOKEN_MANAGER_ETH_ADDRESS = "0xd2AaA00400000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ETH_ABI = [ "function exitToMain(uint256 amount) external" ]; const ONE_HUNDRED_TOKENS = parseEther("100"); // 100 tokens in wei format // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(SKALE_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerETH = new Contract(TOKEN_MANAGER_ETH_ADDRESS, TOKEN_MANAGER_ETH_ABI, wallet); const ethERC20Contract = new Contract(ETH_ERC20_ADDRESS, ERC20_ABI, wallet); // 1. Approve the bridge to move ERC-20 on your behalf const approvalTx = await ethERC20Contract.approve(TOKEN_MANAGER_ETH_ADDRESS, ONE_HUNDRED_TOKENS); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Transfer ERC-20 into bridge, will recieve on the same address on Ethereum const exitTx = await tokenManagerETH.exitToMain(ONE_HUNDRED_TOKENS); await exitTx.wait(1); // Success! Now watch for delivery on Ethereum console.log("Success!"); ``` If your bridge is not working, it is most likely for one of three reasons: 1. You forgot to prepay for gas 2. You forgot to approve the SKALE Bridge on the ETH ERC-20 token 3. You don't have sufficient funds to bridge Still having issues? Join us in [Discord](https://discord.gg/skale) for support! # Connect Custom Contracts Source: https://docs.skale.space/developers/skale-bridge/messaging-layer/connect-custom-contracts Learn how to connect your custom contracts via IMA The following requires `EXTRA_CONTRACT_REGISTRAR_ROLE` on the account calling **registerExtraContract**. ## Adding Extra Contract Registrar Role on SKALE Chain 1. Encode the function call via [multisigwallet-cli](/developers/run-a-skale-chain/using-multisig-wallet-cli) ```shell theme={null} npx msig encodeData [SKALE Chain-name] MessageProxyForSchain grantRole 0x6155b5aac15ce9aa193c0527a6f43be0a36a7e2e7496c2b615c0e5f922842773 [0x_ACCOUNT_TO_GRANT_EXTRA_CONTRACT_REGISTRAR_ROLE_TO] ``` 2. Execute via SAFE by following the steps [here](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) ```ts theme={null} /** * Run: * npm install ethers dotenv */ import { Contract, JsonRpcProvider, Wallet } from "ethers"; import dotenv from "dotenv"; dotenv.config(); const MSG_PROXY_SCHAIN_ADDR = "0xd2AAa00100000000000000000000000000000000"; const MSG_PROXY_SCHAIN_ABI = [ "function grantRole(bytes32 role, address account) external" ]; const provider = new JsonRpcProvider(process.env.RPC_URL); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(MSG_PROXY_SCHAIN_ADDR, MSG_PROXY_SCHAIN_ABI, wallet); const res = await contract.grantRole(id("EXTRA_CONTRACT_REGISTRAR_ROLE", "0x...")); ``` This is required since the SKALE Chain does default to giving Marionette EXTRA\_CONTRACT\_REGISTRAR\_ROLE ## Registering Contracts on a SKALE Chain * **via SAFE** -- would be if you give Marionette EXTRA\_CONTRACT\_REGISTRAR\_ROLE on MessageProxyForSchain * **via Ethers v6** -- would be if you gave an EOA the role and use Ethers 1. Encode the function call via [multisigwallet-cli](/developers/run-a-skale-chain/using-multisig-wallet-cli) ```shell theme={null} npx msig encodeData juicy-low-small-testnet MessageProxyForSchain registerExtraContract destination-skale-chain-name local-contract-address ``` 2. Execute via SAFE by following the steps [here](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) ```ts theme={null} /** * Run: * npm install ethers dotenv */ import { Contract, JsonRpcProvider, Wallet } from "ethers"; import dotenv from "dotenv"; dotenv.config(); const MSG_PROXY_SCHAIN_ADDR = "0xd2AAa00100000000000000000000000000000000"; const MSG_PROXY_SCHAIN_ABI = [ "function registerExtraContract(string memory chainName, address extraContract)" ]; const provider = new JsonRpcProvider(process.env.RPC_URL); const wallet = new Wallet(process.env.PRIVATE_KEY, provider); const contract = new Contract(MSG_PROXY_SCHAIN_ADDR, MSG_PROXY_SCHAIN_ABI, wallet); const res = await contract.registerExtraContract("elated-tan-skat", "0x...")); // elated-tan-skat is Europa Mainnet ``` ## Registering Contracts on Ethereum 1. Go to [SAFE App](https://safe.global) or your preferred frontend for SAFE. 2. Press **New Transaction** and then **Transaction Builder** Click on Transaction Builder 3. Input the MessageProxy address for your correct environment into the **Contract Address** field and select **Use Implementation ABI** Each SKALE Manager deployment -- Mainnet, Testnet, etc. -- has a smart contract called MessageProxy which is what sends data between Ethereum and SKALE Chains. Grab the correct address from the table below and verify by visiting the SKALE Network [Releases Repo](https://github.com/skalenetwork/concepts/tree/master/releases). **MessageProxy Contract Addresses** | Network | Contract Address | | ------------------------ | ------------------------------------------ | | Ethereum Mainnet | 0x8629703a9903515818C2FeB45a6f6fA5df8Da404 | | Ethereum Holesky Testnet | 0x682ef859e1cE314ceD13A6FA32cE77AaeCE98e28 | 4. Select `registerExtraContract` from the dropdown list in *Contract Method Selector*, set the *schainName (string)* field (e.g elated-tan-skat), and set the *extraContract (address)* field which is the smart contract on Ethereum Input transaction details 5. Click **Add transaction**, scroll down, and click **Create Batch**, then click **Send Batch**. [Tenderly](https://tenderly.co/) simulation is generally available, even on testnet, and is encouraged to be used to see all the changes occuring on the Ethereum side before they occur. # Create Custom Contracts Source: https://docs.skale.space/developers/skale-bridge/messaging-layer/create-custom-contracts Learn how to create contracts that use SKALE's IMA Messaging Layer The following will guide you through an example implementation of using SKALE's Native Messaging Layer to send custom information back and forth between SKALE Chains. The following implements a bi-directional flow of data to showcase both sending and receiving clearly, however, contracts can easily implement a one-way flow. ## Custom Contract ```solidity theme={null} // SDPX-License-Identifier: MIT pragma solidity 0.8.9; import { IMessageProxyForSchain } from "./IMessageProxyForSchain.sol"; contract SendNumber { /// @notice lastNumber is the last value recieved via IMA. This is for testing purposes uint256 public lastNumber; /// @notice the dstChainHash is the keccak256 hash of the SKALE Chain name i.e keccak256(abi.encodePacked("elated-tan-skat")) bytes32 public dstChainHash; /// @notice the dstContractAddr is the address of the receiving contract on the other chain address public dstContractAddr; /// @notice messageProxy is a predeployed contract on all SKALE Chains IMessageProxyForSchain public messageProxy = IMessageProxyForSchain(0xd2AAa00100000000000000000000000000000000); event NumberReceived(uint256 indexed number, address indexed sender); constructor( bytes32 _dstChainHash, address _dstContractAddr ) { dstChainHash = _dstChainHash; dstContractAddr = _dstContractAddr; } function sendNumberToChain(uint256 number) external { /// this is how you post a message to IMA messageProxy.postOutgoingMessage( dstChainHash, dstContractAddr, abi.encode(number) ); } /// @notice this is how you recieve a message from IMA function postMessage( bytes32 schainHash, address sender, bytes calldata data ) external { if (schainHash != dstChainHash) revert("Invalid Chain Hash"); (uint256 number) = abi.decode(data, (uint256)); lastNumber = number; emit NumberReceived(number, sender); } } ``` ## Key Notes * This contract is both a sender and receiver via IMA * This means it implements both postOutgoingMessage via MessageProxy interface AND postMessage which is the receiving part * This contract takes two constructor arguments to set the destination (dst) chain and contract which are later used to check and validate messages Don't forget to register your smart contracts on MessageProxyForSchain next! See [Connect Custom Contracts](/developers/skale-bridge/messaging-layer/connect-custom-contracts) for next steps! # Message Proxy Source: https://docs.skale.space/developers/skale-bridge/messaging-layer/message-proxy Using SKALE MessageProxy to connect blockchains A message proxy within the SKALE Ecosystem is a key "connection" point that works to send and receive messages between Ethereum and SKALE Chains. **MessageProxy** is an abstract smart contract that is extended into two key smart contracts used as cornerstones of the SKALE IMA Messaging Layer: 1. **MessageProxyForMainnet** -- the MessageProxy deployed on the Ethereum Mainnet with an architecture designed for posting messages to SKALE Chains 2. **MessageProxyForSchain** -- the MessageProxy that is pre-deployed on every SKALE Chain that enables communication with Ethereum Mainnet AND communication SKALE Chain to SKALE Chain > MessageProxy is used to send messages to any contracts. It is used within the bridging layer to actually prepare and execute messages for transportation between between chains. ## Architecture * MessageProxy contracts are deployed on Mainnet, MessageProxyForMainnet, and each SKALE Chain, MessageProxyForSchain. * Contracts must be registered to a MessageProxy which allows them to interact with IMA Agent * Messages sent via MessageProxy are processed by the [IMA Agent](#ima-agent). * Messages received via MessageProxy are forwarded to the contract contract and executed MessageProxyForSchain is deployed to **0xd2AAa00100000000000000000000000000000000** on EVERY SKALE Chain. ## Basic Implementation Utilize the following base flow to send a message: ```solidity theme={null} interface IMessageProxy { function postOutgoingMessage( bytes32 targetChainHash, address targetContract, bytes calldata data ) external; } contract ExampleSendContract { IMessageProxy private messageProxy = IMessageProxy(0xd2AAa00100000000000000000000000000000000); // Use this address for SKALE Chain, otherwise use the proper Mainnet Address function sendMessageToMainnet(address targetContracts, bytes calldata data) external { messageProxy.postOutgoingMessage(keccak256(abi.encodePacked("Mainnet")), targetContract, data); } } ``` Utilize the following base flow to receive a message: ```solidity theme={null} contract ExampleReceiveContract { function postMessage( bytes32 schainHash, address sender, bytes calldata data ) external returns (address) { [add in your processing logic] } } ``` Reminder to register the contracts and ensure if going SKALE Chain to SKALE Chain that the chains are connected first. ## IMA Agent The IMA Agent is a service that runs within each SKALE Supernode that handles watching for transactions that are executed via MessageProxy\* and processes them as messages accordingly. The agent automatically utilizes the nodes trusted execution environment (TEE) through Intel SGX© to handle the processing of messages. ## Managing IMA IMA, similar to other predeployed contracts running on SKALE, is a highly customizable and managed by the SKALE Chain Owner/Operator or accounts delegated to them. The following are managed by the SKALE Chain Owner: ### Message Size Limit The default message size limit is 1,000,000 gas units. This limit can be changed on each individual SKALE Chain with the following process: 1. Assign `CONSTANT_SETTER_ROLE`, an OpenZeppelin-based smart contract role, to one of your multisignature wallets or trusted delegated accounts. This call can be made by the [SKALE Chain Owner](/developers/run-a-skale-chain/using-safe) or directly if another account is able to assign this role to your contract. ```shell theme={null} npx msig encodeData SKALE Chain-name-here MessageProxyForSchain grantRole 0x96e3fc3be15159903e053027cff8a23f39a990e0194abcd8ac1cf1b355b8b93c [0x-wallet-address-of-executor] ``` ```ts theme={null} /** * Run: * npm install ethers dotenv */ import { Contract, JsonRpcProvider, Wallet } from "ethers"; import dotenv from "dotenv"; dotenv.config(); const MSG_PROXY_SCHAIN_ADDR = "0xd2AAa00100000000000000000000000000000000"; const MSG_PROXY_SCHAIN_ABI = [ "function grantRole(bytes32 role, address account) external", "function setNewGasLimit(uint256 newGasLimit) external" ]; const provider = new JsonRpcProvider(process.env.RPC_URL); const wallet = new Wallet(process.env.PRIVATE_KEY); const contract = new Contract(MSG_PROXY_SCHAIN_ADDR, MSG_PROXY_SCHAIN_ABI, wallet); const res = await contract.grantRole("0x96e3fc3be15159903e053027cff8a23f39a990e0194abcd8ac1cf1b355b8b93c", "0x[ACCOUNT_TO_GRANT_ROLE]"); ``` 2. Once assigned, use your account to set the new gas limit. ```shell theme={null} npx msig encodeData schain-name-here MessageProxyForSchain setNewGasLimit 3000000 ``` ```ts theme={null} /** * Run: * npm install ethers dotenv */ import { Contract, JsonRpcProvider, Wallet } from "ethers"; import dotenv from "dotenv"; dotenv.config(); const MSG_PROXY_SCHAIN_ADDR = "0xd2AAa00100000000000000000000000000000000"; const MSG_PROXY_SCHAIN_ABI = [ "function grantRole(bytes32 role, address account) external", "function setNewGasLimit(uint256 newGasLimit) external" ]; const provider = new JsonRpcProvider(process.env.RPC_URL); const wallet = new Wallet(process.env.PRIVATE_KEY); const contract = new Contract(MSG_PROXY_SCHAIN_ADDR, MSG_PROXY_SCHAIN_ABI, wallet); const res = await contract.setNewGasLimit(3000000); // Set to 3,0000,000 ``` # Using Custom Contracts Source: https://docs.skale.space/developers/skale-bridge/messaging-layer/using-custom-contracts Using Custom SKALE IMA Contracts ## Recap 1. We have created a [custom contract](/developers/skale-bridge/messaging-layer/create-custom-contracts) 2. We have [connected](/developers/skale-bridge/messaging-layer/connect-custom-contracts) the custom Contracts 3. Now we are ready to test them! Follow the steps below. Having trouble setting up or using your custom IMA connected contracts? Join the team in [Discord](https://discord.gg/skale) for suppport! ```ts theme={null} /** * Run: * npm install ethers dotenv */ import { Contract, JsonRpcProvider, Wallet } from "ethers"; import dotenv from "dotenv"; dotenv.config(); const SEND_NUMBER_ADDR = "0xd2AAa00100000000000000000000000000000000"; const SEND_NUMBER_ABI = [ "function sendNumber(uint256 number) external", "function lastNumber() external view returns (uint256)" ]; const sChainAProvider = new JsonRpcProvider(process.env.RPC_URL); const sChainBProvider = new JsonRpcProvider(process.env.PRC_URL); const wallet = new Wallet(process.env.PRIVATE_KEY, sChainAProvider); const contractA = new Contract(SEND_NUMBER_ADDR_A, SEND_NUMBER_ABI, wallet); const contractB = new Contract(SEND_NUMBER_ADDR_B, SEND_NUMBER_ABI, sChainBProvider); const txA = await contractA.sendNumber(BigInt(5)); await txA.ok(); // The following can be replaced with "listening for an event", however, // a while loop is more intuitive for a new developer while (true) { const lastNumber = await contractB.lastNumber(); if (lastNumber === BigInt(5)) { console.log("Posted!"); break; } await new Promise(res => setTimeout(res, 2000)); // Sleep 2 seconds } ``` # Bridge Overview Source: https://docs.skale.space/developers/skale-bridge/overview Complete guide to seamless asset bridging on SKALE Network - Zero gas fees, instant finality, and enterprise-grade security > **Transform your blockchain experience with SKALE's revolutionary bridging and messaging technology** Welcome to the future of blockchain interoperability! SKALE's Interchain Messaging Agent (IMA) provides the most advanced, secure, and cost-effective solution for moving assets between Ethereum and SKALE chains. ## Why Choose SKALE Bridge? ### **Zero Gas Fees** Say goodbye to expensive transaction costs. Bridge your assets and transact freely without worrying about gas fees eating into your profits when bridging SKALE Chain to SKALE Chain. ### **Instant Finality** Experience lightning-fast transactions with thanks to SKALE's incredibly fast block times averaging \~1 second. Most messages and bridging transactions deliver in less than 30 seconds! ### **Decentralized Security** SKALE boasts one of the **ONLY** native bridges that is truly decentralized. IMA directly leverages the validators, threshold encryption design, and supermajority requirements that make SKALE Consensus so fast and secure. ### **Full EVM Compatibility** Seamlessly migrate your existing Ethereum applications without code changes while gaining massive performance improvements PLUS bring your tokens and NFTs! ## What is IMA? SKALE's Interchain Messaging Agent (IMA) is a decentralized, high-performance message transport layer and bridging protocol. The unique design and flexiblity of IMA allows every SKALE Chain to be joined together into an interconnected web of blockchains that make up the SKALE Network. ## 📚 Complete Bridging Guide ### **Core Bridging to/from Ethereum** * **[Bridge ETH](/developers/skale-bridge/ethereum/bridge-eth)** - Transfer native Ethereum to a SKALE Chain * **[Bridge ERC-20 Tokens](/developers/skale-bridge/ethereum/bridge-erc20)** - Move fungible tokens like USDC, WBTC, and more to a SKALE Chain * **[Bridge ERC-721 NFTs](/developers/skale-bridge/ethereum/bridge-erc721)** - Seamlessly transfer unique digital assets to SKALE * **[Bridge ERC-1155 Tokens](/developers/skale-bridge/ethereum/bridge-erc1155)** - Bring multi-token standard tokens to SKALE ### **Core Bridging SKALE Chain to SKALE Chain** * **[Bridge ERC-20 Tokens](/developers/skale-bridge/skale/bridge-erc20)** - Move fungible tokens like USDC, WBTC, and more between SKALE Chains * **[Bridge ERC-721 NFTs](/developers/skale-bridge/skale/bridge-erc721)** - Seamlessly transfer unique digital assets between SKALE Chains * **[Bridge ERC-1155 Tokens](/developers/skale-bridge/skale/bridge-erc1155)** - Bring multi-token standard tokens between SKALE Chains ### **Advanced Features** * **[Custom Contract Integration](/developers/skale-bridge/messaging-layer/create-custom-contracts)** - Build multi-chain applications with ease * **[Connect Existing Contracts](/developers/skale-bridge/messaging-layer/connect-custom-contracts)** - Connect multi-chain applications ### **Support & Troubleshooting** * **[Developer Support](https://discord.gg/skale)** - Get assistance from the SKALE Team # Bridge ERC-1155 Tokens Source: https://docs.skale.space/developers/skale-bridge/skale/bridge-erc1155 Guide on bridging ERC-1155 tokens on SKALE SKALE's Interchain Messaging Agent (IMA) Bridge allows for direct communication and bridging between SKALE Chains (without ever going to Mainnet). The following walks you through setting up an ERC-1155 token between two SKALE Chains and how to programatically bridge. ## Important Information * When a SKALE Chain is created, there are no ERC-1155 tokens mapped by default * A token being bridged between two chains should have its supply **issued** (i.e minted) on one chain. The second SKALE Chain mints by design via IMA, however, the token should not be mintable any other way * Tokens being bridged from SKALE Chain to SKALE Chain are locked in TokenManagerERC1155 on the origin chain * Tokens being bridged from SKALE Chain to SKALE Chain are minted by IMA on the destination chain ## Bridge Setup If the SKALE Chains you are mapping the ERC-1155 token from/to are not connected, please [connect the SKALE Chains](/developers/skale-bridge/skale/connect-schains). ### 1. Prepare the ERC-1155 ERC-1155 tokens that are being bridged between SKALE Chains should have the **mint** function that is locked down to TokenManagerERC1155. If the mint function is not locked down to just the IMA Bridge, specifically TokenManagerERC1155; there is a chance that the existing tokens on the SKALE Chain may not match what is secured in the bridge. If the burn function is not locked down to just the IMA Bridge, specifically TokenManagerERC1155; there is a chance that the supply on the SKALE Chain side could be "burned", while the tokens still exist on the original SKALE Chain which could later be withdrawn under certain circumstances. It is recommended that explicit burns on a SKALE Chain should be avoided for bridged tokens. The following is the base interchain token code that meets the above bridging requirements for IMA. It is not recommended to use this directly, but to use a wrapper. See **YourInterchainNFT.sol** for an example. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.9; import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155.sol"; import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; contract InterchainERC1155 is AccessControl, ERC1155 { using Strings for uint256; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); string private _baseUri; constructor(string memory baseUri) ERC1155("") { _grantRole(MINTER_ROLE, 0xD2aaA00900000000000000000000000000000000); _baseUri = baseUri; // e.g. "ipfs://QmSomeCID/" } function uri(uint256 tokenId) public view override returns (string memory) { return string(abi.encodePacked(_baseUri, tokenId.toString(), ".json")); } function mint( address account, uint256 id, uint256 amount, bytes memory data ) external { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _mint(account, id, amount, data); } function mintBatch( address account, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) external { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _mintBatch(account, ids, amounts, data); } function setBaseUri(string memory newBaseUri) external onlyRole(DEFAULT_ADMIN_ROLE) { _baseUri = newBaseUri; } } ``` ### 2. Deploy the ERC-1155 on SKALE Utilize your preferred tooling i.e [Foundry](https://getfoundry.sh), [Hardhat](https://hardhat.org), [Remix](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=paris\&version=soljson-v0.8.19+commit.7dd6d404.js), etc. to deploy your IMA compatible ERC-1155 token to the SKALE Chain you want to be able to bridge assets too. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC1155 } from "./InterchainERC1155.sol"; contract InGameTokens is InterchainERC1155("ipfs://") {} ``` > *InterchainERC1155.sol is inherited from the code above* ### 3. Map the SKALE Token If you are trying to setup a token on SKALE in a production environment please join us in [Discord](https://discord.gg/skale) for support! Make sure to replace the values with your SKALE Chain name and the correct token addresses. #### Add the token on SKALE Chain TokenManagerERC1155 * **DST\_SCHAIN\_NAME** is the name of the SKALE Chain that the transaction should execute on * **ORIGIN\_SCHAIN\_NAME** is the name of the SKALE Chain that the token is being mapped from * **0x\_ORIGIN\_TOKEN** is the original token address on the ORIGIN\_SCHAIN\_NAME * **0x\_DST\_TOKEN** is the destination token address on the DST\_SCHAIN\_NAME ## Multisigwallet CLI ```shell theme={null} npx msig encodeData [DST_SCHAIN_NAME] TokenManagerERC1155 addERC20TokenByOwner [ORIGIN_SCHAIN_NAME] [0x_ORIGIN_TOKEN] [0x_DST_TOKEN] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) #### Verify the Mapping To verify the mapping, you should have an event emitted from TokenManagerERC1155 on SKALE Chain - `event ERC1155TokenAdded(SchainHash indexed chainHash, address indexed erc1155OnMainChain, address indexed erc1155OnSchain);` ## Bridging ERC-1155 The following does not require you to setup your own token. This works with **ANY** ERC-1155 token that is mapped from a SKALE Chain to any other SKALE Chain as long as the actual ERC-1155 token on each side does not have additional restrictions around who can transfer. The flow for bridging an ERC-1155 from SKALE Chain to SKALE Chain follows a very similar flow to a standard ERC-1155 transfer: 1. Approve the bridge contract on the ERC-1155 token to allow it to move your NFTs 2. Call the bridge directly to transfer the specific ERC-1155 from SKALE Chain -> SKALE Chain, if the mapping exists 3. Wait for the message to be posted by the validator set on the SKALE Chain, which is the net-new minted NFT corresponding to the NFT (by id) locked on the origin SKALE Chain during the bridge ### Bridge Tokens The following will help you bridge an ERC-1155 NFT from one SKALE Chain to another. ```js theme={null} import { Contract, JsonRpcProvider, Wallet } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ORIGIN_SCHAIN_RPC_URL = "[YOUR_RPC_URL]"; const ERC1155_ADDRESS = "[ORIGIN_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const TOKEN_MANAGER_ERC1155_ADDRESS = "0xD2aaA00900000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC1155_ABI = [ "function transferToSchainERC1155(string calldata targetSchainName, address contractOnMainnet, uint256 id, uint256 amount) external" ]; const DST_SKALE_CHAIN_NAME = "[DST_SKALE_CHAIN_NAME]"; // e.g green-giddy-denebola (nebula mainnnet); const NUMBER_TOKENS_TO_TRANSFER = parseEther("100"); // 100 tokens in wei format const TOKEN_ID = BigInt(1); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ORIGIN_SCHAIN_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerContract = new Contract(TOKEN_MANAGER_ERC1155_ADDRESS, TOKEN_MANAGER_ERC1155_ABI, wallet); const tokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ERC-1155 Tokens const approvalTx = await tokenContract.setApprovalForAll(TOKEN_MANAGER_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Bridge 100 of ERC-1155 Token Id #1, will receive on same address on new skale chain const bridgeTx = await tokenManagerContract.transferToSchainERC1155(DST_SKALE_CHAIN_NAME, ERC1155_ADDRESS, TOKEN_ID, NUMBER_TOKENS_TO_TRANSFER); await bridgeTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // Success! Now watch for delivery on Destination Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ORIGIN_SCHAIN_RPC_URL = "[YOUR_RPC_URL]"; const ERC1155_ADDRESS = "[ORIGIN_TOKEN_ADDRESS]"; const ERC1155_ABI = [ "function setApprovalForAll(address operator, bool approved) external" ]; const TOKEN_MANAGER_ERC1155_ADDRESS = "0xD2aaA00900000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC1155_ABI = [ "function transferToSchainERC1155Batch(string calldata targetSchainName, address contractOnMainnet, uint256[] calldata ids, uint256[] calldata amounts) external" ]; const DST_SKALE_CHAIN_NAME = "[DST_SKALE_CHAIN_NAME]"; // e.g green-giddy-denebola (nebula mainnnet); const NUMBER_TOKENS_TO_TRANSFER = [parseEther("5"), parseEther("100")]; // 100 tokens in wei format const TOKEN_IDS = [BigInt(1), BigInt(5)]; // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ORIGIN_SCHAIN_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerContract = new Contract(TOKEN_MANAGER_ERC1155_ADDRESS, TOKEN_MANAGER_ERC1155_ABI, wallet); const tokenContract = new Contract(ERC1155_ADDRESS, ERC1155_ABI, wallet); // 1. Approve the bridge to move ERC-1155 Tokens const approvalTx = await tokenContract.setApprovalForAll(TOKEN_MANAGER_ERC1155_ADDRESS, true); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Bridge 5 of ERC-1155 Token Id #1 and 100 of Token Id #5, will receive on same address on new skale chain const bridgeTx = await tokenManagerContract.transferToSchainERC1155Batch(DST_SKALE_CHAIN_NAME, ERC1155_ADDRESS, TOKEN_IDS, NUMBER_TOKENS_TO_TRANSFER); await bridgeTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // Success! Now watch for delivery on Destination Chain console.log("Success!"); ``` # Bridge ERC-20 Tokens Source: https://docs.skale.space/developers/skale-bridge/skale/bridge-erc20 Guide on bridging ERC-20 tokens on SKALE SKALE's Interchain Messaging Agent (IMA) Bridge allows for direct communication and bridging between SKALE Chains (without ever going to Mainnet). The following walks you through setting up an ERC-20 token between two SKALE Chains and how to programatically bridge. ## Important Information * When a SKALE Chain is created, there are no ERC-20 tokens mapped by default * A token being bridged between two chains should have its supply **issued** (i.e minted) on one chain. The second SKALE Chain mints by design via IMA, however, the token should not be mintable any other way * Tokens being bridged from SKALE Chain to SKALE Chain are locked in TokenManagerERC20 on the origin chain * Tokens being bridged from SKALE Chain to SKALE Chain are minted by IMA on the destination chain ## Bridge Setup If the SKALE Chains you are mapping the ERC-20 token from/to are not connected, please [connect the SKALE Chains](/developers/skale-bridge/skale/connect-schains). ### 1. Prepare the ERC-20 ERC-20 tokens that are being bridged between SKALE Chains should follow two basic requirements in order to be compatible with the SKALE IMA bridging layer: 1. Have a **mint** function that is locked down to TokenManagerERC20 2. Have a **burn** function that is locked down to TokenManagerERC20 If the mint function is not locked down to just TokenManagerERC20 there is a chance that the supply on the desintation chain could be larger than what is actually locked in the bridge on the origin SKALE Chain. If the burn function is not locked down to just TokenManagerERC20 there is a chance that the supply on the SKALE Chain side could be "burned", while the tokens still exist on the origin SKALE Chain which could later be withdrawn under certain circumstances. It is recommended that explicit burns on a SKALE Chain should be avoided for the bridged tokens. The following is the base interchain token code that meets the above bridging requirements for IMA. It is not recommended to use this directly, but to use a wrapper. See InterchainSKL for an 18 decimal example and InterchainUSDT for a 6 decimal example. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; // Importing the ERC20 standard contract and AccessControl for role-based access management. import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; /** * @title InterchainERC20 * @dev This contract is an ERC20 token implementation with role-based access control for minting and burning. * It utilizes OpenZeppelin's ERC20 and AccessControl for functionality. */ contract InterchainERC20 is ERC20, AccessControl { // Define roles using hashed constants for efficient comparison. bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); constructor(string memory name, string memory symbol) ERC20(name, symbol) { // Assign the minter role to a predefined address. _grantRole(MINTER_ROLE, 0xD2aAA00500000000000000000000000000000000); // Assign the burner role to a predefined address. _grantRole(BURNER_ROLE, 0xD2aAA00500000000000000000000000000000000); } function mint(address to, uint256 amount) public virtual { // Ensure that the caller has the MINTER_ROLE. require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); // Mint the specified amount of tokens to the target address. _mint(to, amount); } function burn(uint256 amount) public virtual { // Ensure that the caller has the BURNER_ROLE. require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner"); // Burn the specified amount of tokens from the caller's account. _burn(msg.sender, amount); } } ``` ### 2. Deploy the ERC-20 on SKALE Chain Utilize your preferred tooling i.e [Foundry](https://getfoundry.sh), [Hardhat](https://hardhat.org), [Remix](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=paris\&version=soljson-v0.8.19+commit.7dd6d404.js), etc. to deploy your IMA compatible ERC-20 token to the SKALE Chain you want to be able to bridge assets too. ## InterchainSKL.sol *This is an example of an 18 decimal ERC-20 token that would be deployed on SKALE* ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC20 } from "./InterchainERC20.sol"; contract InterchainSKL is InterchainERC20("Skale Token", "SKL") {} ``` *This is an example of an 6 decimal ERC-20 token that would be deployed on SKALE* ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC20 } from "./InterchainERC20.sol"; contract InterchainUSDT is InterchainERC20("Tether USD", "USDT") { function decimals() public view override returns (uint8) { return 6; } } ``` > *InterchainERC20.sol is inherited from the code above* ### 3. Map the SKALE Token #### Add the token on SKALE Chain TokenManagerERC20 * **DST\_SCHAIN\_NAME** is the name of the SKALE Chain that the transaction should execute on * **ORIGIN\_SCHAIN\_NAME** is the name of the SKALE Chain that the token is being mapped from * **0x\_ORIGIN\_TOKEN** is the original token address on the ORIGIN\_SCHAIN\_NAME * **0x\_DST\_TOKEN** is the destination token address on the DST\_SCHAIN\_NAME ```shell theme={null} npx msig encodeData [DST_SCHAIN_NAME] TokenManagerERC20 addERC20TokenByOwner [ORIGIN_SCHAIN_NAME] [0x_ORIGIN_TOKEN] [0x_DST_TOKEN] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) #### Verify the mapping To verify the mapping, TokenManagerERC20 on the SKALE Chain (DST\_CHAIN\_NAME from above) should emit an event - `event ERC20TokenAdded(SchainHash indexed chainHash, address indexed erc20OnMainChain, address indexed erc20OnSchain);` ## Bridging ERC-20 The following does not require you to setup your own token. This works with **ANY** ERC-20 token that is mapped from a SKALE Chain to any other SKALE Chain as long as the actual ERC-20 token on each side does not have additional restrictions around who can transfer. The flow for bridging an ERC-20 from SKALE Chain to SKALE Chain follows a very similar flow to a standard ERC-20 transfer: 1. Approve the origin chain bridge contract on the ERC-20 token to allow it to control some amount of funds 2. Call the origin bridge directly to transfer the asset from SKALE Chain -> SKALE Chain 3. Wait for the message to be posted by the validator set on the destination SKALE Chain, which is where the net-new minted tokens corresponding to the value locked on the origin SKALE Chain during the bridge are created ### Bridge Tokens If bridging a token nativley deployed on a SKALE Chain to another SKALE Chain, the process for bridging in either direction is identical. The action taken by the chain is slightly different (i.e lock and mint vs burn and unlock), however, for the end user the flow is identical i.e receive N new tokens in their wallet. A normal ERC-20 bridge will result in tokens being delivered to the same wallet on the new SKALE Chain. A direct ERC-20 bridge will result in tokens being delivered to the specified wallet on the new SKALE Chain. ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ORIGIN_SCHAIN_RPC_URL = "[YOUR_RPC_URL]"; const ERC20_ADDRESS = "[ORIGIN_TOKEN_ADDRESS]"; const ERC20_ABI = [ "function approve(address spender, uint256 amount) external" ]; const TOKEN_MANAGER_ERC20_ADDRESS = "0xD2aAA00500000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC20_ABI = [ "function transferToSchainERC20(string calldata targetSchainName, address contractOnMainnet, uint256 amount) external" ]; const DST_SKALE_CHAIN_NAME = "[DST_SKALE_CHAIN_NAME]"; // e.g green-giddy-denebola (nebula mainnnet); const NUMBER_TOKENS_TO_TRANSFER = parseEther("100"); // 100 tokens in wei format // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ORIGIN_SCHAIN_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerContract = new Contract(TOKEN_MANAGER_ERC20_ADDRESS, TOKEN_MANAGER_ERC20_ABI, wallet); const tokenContract = new Contract(ERC20_ADDRESS, ERC20_ABI, wallet); // 1. Approve the bridge to move ERC-20 const approvalTx = await tokenContract.approve(TOKEN_MANAGER_ERC20_ADDRESS, NUMBER_TOKENS_TO_TRANSFER); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1s seconds // 2. Deposit ERC-20 into bridge, will receive on same address on SKALE const bridgeTx = await tokenManagerContract.transferToSchainERC20(DST_SKALE_CHAIN_NAME, ERC20_ADDRESS, NUMBER_TOKENS_TO_TRANSFER); await bridgeTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // Success! Now watch for delivery on Destination Chain console.log("Success!"); ``` ```js theme={null} import { Contract, JsonRpcProvider, Wallet, parseEther } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ORIGIN_SCHAIN_RPC_URL = "[YOUR_RPC_URL]"; const ERC20_ADDRESS = "[ORIGIN_TOKEN_ADDRESS]"; const ERC20_ABI = [ "function approve(address spender, uint256 amount) external" ]; const TOKEN_MANAGER_ERC20_ADDRESS = "0xD2aAA00500000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC20_ABI = [ "function transferToSchainERC20Direct(string calldata targetSchainName, address contractOnMainnet, uint256 amount, address receiver) external" ]; const DST_SKALE_CHAIN_NAME = "[DST_SKALE_CHAIN_NAME]"; // e.g green-giddy-denebola (nebula mainnnet); const NUMBER_TOKENS_TO_TRANSFER = parseEther("100"); // 100 tokens in wei format const CUSTOM_RECEIVER_ADDRESS = "[CUSTOM_RECEIVER_ADDRESS]"; // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ORIGIN_SCHAIN_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerContract = new Contract(TOKEN_MANAGER_ERC20_ADDRESS, TOKEN_MANAGER_ERC20_ABI, wallet); const tokenContract = new Contract(ERC20_ADDRESS, ERC20_ABI, wallet); // 1. Approve the bridge to move ERC-20 const approvalTx = await tokenContract.approve(TOKEN_MANAGER_ERC20_ADDRESS, NUMBER_TOKENS_TO_TRANSFER); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Deposit ERC-20 into bridge, will receive on the custom receiver address on SKALE const bridgeTx = await tokenManagerContract.transferToSchainERC20Direct(DST_SKALE_CHAIN_NAME, ERC20_ADDRESS, NUMBER_TOKENS_TO_TRANSFER, CUSTOM_RECEIVER_ADDRESS); await bridgeTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // Success! Now watch for delivery on Destination Chain console.log("Success!"); ``` # Bridge ERC-721 Tokens Source: https://docs.skale.space/developers/skale-bridge/skale/bridge-erc721 Guide on bridging ERC-721 tokens on SKALE SKALE's Interchain Messaging Agent (IMA) Bridge allows for direct communication and bridging between SKALE Chains (without ever going to Mainnet). The following walks you through setting up an ERC-721 token between two SKALE Chains and how to programatically bridge. ## Important Information * When a SKALE Chain is created, there are no ERC-721 tokens mapped by default * A token being bridged between two chains should have its supply **issued** (i.e minted) on one chain. The second SKALE Chain mints by design via IMA, however, the token should not be mintable any other way * Tokens being bridged from SKALE Chain to SKALE Chain are locked in TokenManagerERC721 on the origin chain * Tokens being bridged from SKALE Chain to SKALE Chain are minted by IMA on the destination chain ## Bridge Setup If the SKALE Chains you are mapping the ERC-721 token from/to are not connected, please [connect the SKALE Chains](/developers/skale-bridge/skale/connect-schains). ### 1. Prepare the ERC-721 ERC-721 tokens that are being bridged between SKALE Chains should have the **mint** function that is locked down to TokenManagerERC721. If the mint function is not locked down to just the IMA Bridge, specifically TokenManagerERC721; there is a chance that the existing tokens on the SKALE Chain may not match what is secured in the bridge. If the burn function is not locked down to just the IMA Bridge, specifically TokenManagerERC721; there is a chance that the supply on the SKALE Chain side could be "burned", while the tokens still exist on the original SKALE Chain which could later be withdrawn under certain circumstances. It is recommended that explicit burns on a SKALE Chain should be avoided for bridged tokens. The following is the base interchain token code that meets the above bridging requirements for IMA. It is not recommended to use this directly, but to use a wrapper. See **YourInterchainNFT.sol** for an example. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; /** * @title InterchainERC721 * @dev ERC-721 with role-based minting and dynamic token URI. */ contract InterchainERC721 is AccessControl, ERC721 { using Strings for uint256; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); string private _baseTokenURI; constructor( string memory contractName, string memory contractSymbol, string memory baseTokenURI ) ERC721(contractName, contractSymbol) { _setRoleAdmin(MINTER_ROLE, 0xD2aaa00600000000000000000000000000000000); // example admin role _grantRole(MINTER_ROLE, _msgSender()); _baseTokenURI = baseTokenURI; } function mint(address to, uint256 tokenId) external returns (bool) { require(hasRole(MINTER_ROLE, _msgSender()), "Sender is not a Minter"); _safeMint(to, tokenId); return true; } /// @notice Override to return dynamic token URI function tokenURI(uint256 tokenId) public view override returns (string memory) { require(_exists(tokenId), "ERC721: URI query for nonexistent token"); return string(abi.encodePacked(_baseTokenURI, tokenId.toString(), ".json")); } /// @notice Allows admin to update base URI function setBaseURI(string calldata newBaseURI) external onlyRole(MINTER_ROLE) { _baseTokenURI = newBaseURI; } } ``` ### 2. Deploy the ERC-721 on SKALE Utilize your preferred tooling i.e [Foundry](https://getfoundry.sh), [Hardhat](https://hardhat.org), [Remix](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=paris\&version=soljson-v0.8.19+commit.7dd6d404.js), etc. to deploy your IMA compatible ERC-721 token to the SKALE Chain you want to be able to bridge assets too. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity 0.8.19; import { InterchainERC721 } from "./InterchainERC721.sol"; contract InterchainNFT is InterchainERC721("NFT Name", "NFT Symbol", "ipfs:///") {} ``` > *InterchainERC721.sol is inherited from the code above* ### 3. Map the SKALE Token If you are trying to setup a token on SKALE in a production environment please join us in [Discord](https://discord.gg/skale) for support! #### Add the token on SKALE Chain TokenManagerERC721 * **DST\_SCHAIN\_NAME** is the name of the SKALE Chain that the transaction should execute on * **ORIGIN\_SCHAIN\_NAME** is the name of the SKALE Chain that the token is being mapped from * **0x\_ORIGIN\_TOKEN** is the original token address on the ORIGIN\_SCHAIN\_NAME * **0x\_DST\_TOKEN** is the destination token address on the DST\_SCHAIN\_NAME ```shell theme={null} npx msig encodeData [DST_SCHAIN_NAME] TokenManagerERC721 addERC20TokenByOwner [ORIGIN_SCHAIN_NAME] [0x_ORIGIN_TOKEN] [0x_DST_TOKEN] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) #### Verify the Mapping To verify the mapping, you should have an event emitted from TokenManagerERC721 on SKALE Chain - `event ERC721TokenAdded(SchainHash indexed chainHash, address indexed erc721OnMainChain, address indexed erc721OnSchain);` ## Bridging ERC-721 The following does not require you to setup your own token. This works with **ANY** ERC-721 token that is mapped from a SKALE Chain to other any SKALE Chain as long as the actual ERC-721 token on each side does not have additional restrictions around who can transfer. The flow for bridging an ERC-721 from SKALE Chain to SKALE Chain follows a very similar flow to a standard ERC-721 transfer: 1. Approve the bridge contract on the ERC-721 token to allow it to move your NFTs 2. Call the bridge directly to transfer the specific ERC-721 from SKALE Chain -> SKALE Chain, if the mapping exists 3. Wait for the message to be posted by the validator set on the SKALE Chain, which is the net-new minted NFT corresponding to the NFT (by id) locked on the origin SKALE Chain during the bridge ### Bridge Tokens The following will help you bridge an NFT from one SKALE Chain to another. ```js theme={null} import { Contract, JsonRpcProvider, Wallet } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const ORIGIN_SCHAIN_RPC_URL = "[YOUR_RPC_URL]"; const ERC721_ADDRESS = "[ORIGIN_TOKEN_ADDRESS]"; const ERC721_ABI = [ "function approve(address spender, uint256 tokenId) external" ]; const TOKEN_MANAGER_ERC721_ADDRESS = "0xD2aaa00600000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_ERC721_ABI = [ "function transferToSchainERC721(string calldata targetSchainName, address contractOnMainnet, uint256 tokenId) external" ]; const DST_SKALE_CHAIN_NAME = "[DST_SKALE_CHAIN_NAME]"; // e.g green-giddy-denebola (nebula mainnnet); const TOKEN_ID = BigInt(1); // Setup the RPC Provider to connect to Ethereum const provider = new JsonRpcProvider(ORIGIN_SCHAIN_RPC_URL); // Setup the wallet with your private key and default to the Ethereum provider const wallet = new Wallet(PRIVATE_KEY, provider); // Setup the smart contracts which default to being signed by your wallet and connected on Ethereum const tokenManagerContract = new Contract(TOKEN_MANAGER_ERC721_ADDRESS, TOKEN_MANAGER_ERC721_ABI, wallet); const tokenContract = new Contract(ERC721_ADDRESS, ERC721_ABI, wallet); // 1. Approve the bridge to move ERC-721 Token Id #1 const approvalTx = await tokenContract.approve(TOKEN_MANAGER_ERC721_ADDRESS, TOKEN_ID); await approvalTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // 2. Deposit ERC-721 Token Id #1 into bridge, will receive on same address on SKALE const bridgeTx = await tokenManagerContract.transferToSchainERC721(DST_SKALE_CHAIN_NAME, ERC721_ADDRESS, TOKEN_ID); await bridgeTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // Success! Now watch for delivery on Destination Chain console.log("Success!"); ``` # Connect SKALE Chains Source: https://docs.skale.space/developers/skale-bridge/skale/connect-schains Learn to connect SKALE Chains together for gas free bridging Ready to start bridging ERC-20's, ERC-721's, and ERC-1155's between SKALE Chains? Before you proceed to the setup guides for your target token, read through the following to ensure that the SKALE Chains you are moving assets between are able to communicate. ## Background on Connections All SKALE Chains utilize the same components and contracts to enable bridging. However, while SKALE Chains are all by default connected to Ethereum for operations and bridging; SKALE Chains do not come out of the box with connections to other SKALE Chains. This is both for security since the SKALE architecture does not define what chains can be used for externally, all chains adhere to an appchain first mentality i.e as siloed and secure as possible allowing the chain owner to setup and connect as they see fit. **Interested in setting up your own token on multiple SKALE Chains? Connect the chains now!** The connection process requires **BOTH** SKALE Chain Owners to execute the connection. This ensures connections cannot be made unilaterally by a single chain which greatly bolsters natural security and compliance. Contact the SKALE team in [Discord](https://discord.gg/skale) to ask for help! ## Connect a SKALE Chain The following will create a connection from your chain to the target chain. If the other chain is already connected to your SKALE Chain, you can proceed with token setup and bridging. If the other chain has not yet connected then bridging will not work until completed. ```shell theme={null} npx msig encodeData [SKALE_CHAIN_NAME] TokenManagerLinker connectSchain [TARGET_CHAIN_NAME] ``` After this, execute by following the steps on [Using SAFE](/developers/run-a-skale-chain/using-safe#submit-transaction-to-safe) ```js theme={null} import { Contract, JsonRpcProvider, Wallet } from "ethers"; // npm add ethers const PRIVATE_KEY = "[YOUR_PRIVATE_KEY]"; const SCHAIN_RPC_URL = "[YOUR_RPC_URL]"; const TOKEN_MANAGER_LINKER_ADDRESS = "0xD2aAA00800000000000000000000000000000000"; // DO NOT CHANGE THIS const TOKEN_MANAGER_LINKER_ABI = [ "function connectSchain(string calldata schainName) external" ]; const SCHAIN_NAME = "[TARGET_SKALE_CHAIN_NAME]"; // e.g green-giddy-denebola (nebula mainnnet); // Setup the RPC Provider to connect to SKALE Chain const provider = new JsonRpcProvider(SCHAIN_RPC_URL); // Setup the wallet with your private key and default to the SKALE Chain provider const wallet = new Wallet(PRIVATE_KEY, provider); const tokenManagerLinkerContract = new Contract(TOKEN_MANAGER_LINKER_ADDRESS, TOKEN_MANAGER_LINKER_ABI, wallet); const connectTx = await tokenManagerLinkerContract.connectSchain(SCHAIN_NAME); await connectTx.wait(1); // Wait 1 blocks for confirmation, ~1 seconds // Success! Now watch for delivery on Destination Chain console.log("Success!"); ``` The above call requires **REGISTRAR\_ROLE** which is an OpenZeppelin AccessControl role defined within the TokenManagerLinker smart contract. # Facilitators on SKALE Source: https://docs.skale.space/get-started/agentic-builders/facilitators Explore the available facilitators on SKALE and how they support x402 The facilitation process in x402 -- verification and settlement of payments -- is critical to ensuring the resource is paid for properly. While the process is 100% self-hostable by the seller, hosted facilitators like PayAI and x402 reduce the scope of integration into x402 by letting sellers of resources quickly and easily accept payments without any knowledge of blockchain, sending transactions, or gas fees. On SKALE, facilitators support both SKALE Base (Mainnet) and SKALE Base Sepolia (Testnet) networks, providing a drop-in solution for processing x402 payments. ## Available Facilitators | Name | Endpoint | Cookbook Demo | | | ------- | ---------------------------------------------------------------------- | ---------------------------------------------------- | ------- | | Corbits | [https://facilitator.corbits.dev](https://facilitator.corbits.dev) | [Corbits Demo](/cookbook/facilitators/using-corbits) | | | RelAI | [https://facilitator.x402.fi](https://facilitator.x402.fi) | [RelAI Demo](/cookbook/facilitators/using-relai) | BITE V1 | | Kobaru | [https://gateway.kobaru.io](https://gateway.kobaru.io) | [Kobaru Demo](/cookbook/facilitators/using-kobaru) | BITE v1 | | PayAI | [https://facilitator.payai.network](https://facilitator.payai.network) | [PayAI Demo](/cookbook/facilitators/using-payai) | - | | x402x | [https://facilitator.x402x.dev](https://facilitator.x402x.dev) | [x402x Demo](/cookbook/facilitators/using-x402x) | - | Does your facilitator support SKALE? Submit a PR at [https://github.com/skalenetwork/docs.skale.space](https://github.com/skalenetwork/docs.skale.space) and add your facilitator! ## Run your own Prefer to run your own facilitator? Checkout the detailed documentation [here](/cookbook/x402/facilitator). This does require blockchain knowledge and compute credits/sFUEL depending on your chain, but you can reach the team in Discord at [https://discord.gg/skale](https://discord.gg/skale) for support! # Start with ERC-8004 Source: https://docs.skale.space/get-started/agentic-builders/start-with-erc-8004 Build trustless AI agents with on-chain identity, reputation, and verification ERC-8004 creates a **trust layer for autonomous AI agents** on Ethereum. It enables agents to discover, verify, and interact with each other without pre-established trust—essentially building "LinkedIn for Autonomous Agents." ## What is ERC-8004? ERC-8004 is a standard that provides three lightweight on-chain registries: 1. **Identity Registry** – Agent identification and ownership 2. **Reputation Registry** – Track agent credibility over time 3. **Verification Registry** – Validate agent capabilities and claims This enables **trustless Agent-to-Agent (A2A)** interactions—agents can discover and transact with each other across organizational boundaries without needing prior relationships. ## Quick Start Build your first ERC-8004 agent in 5 minutes. ### Prerequisites ```bash theme={null} npm install ethers@6 ``` ### 1. Deploy or Find Registries ERC-8004 requires three registries. On SKALE, you can: * **Option A**: Deploy your own registries (full control) * **Option B**: Use existing community registries (faster, shared reputation) ```typescript theme={null} // Deploy new registries import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('YOUR_SKALE_RPC_URL'); const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider); // Deploy Identity Registry const IdentityFactory = await ethers.getContractFactory('ERC8004IdentityRegistry'); const identityRegistry = await IdentityFactory.deploy(); await identityRegistry.waitForDeployment(); const identityAddress = await identityRegistry.getAddress(); // Deploy Reputation Registry const ReputationFactory = await ethers.getContractFactory('ERC8004ReputationRegistry'); const reputationRegistry = await ReputationFactory.deploy(); await reputationRegistry.waitForDeployment(); const reputationAddress = await reputationRegistry.getAddress(); // Deploy Verification Registry const VerificationFactory = await ethers.getContractFactory('ERC8004VerificationRegistry'); const verificationRegistry = await VerificationFactory.deploy(); await verificationRegistry.waitForDeployment(); const verificationAddress = await verificationRegistry.getAddress(); console.log({ identityAddress, reputationAddress, verificationAddress }); ``` ### 2. Register Your Agent ```typescript theme={null} const agentId = ethers.keccak256(ethers.toUtf8Bytes('my-first-agent')); const metadata = { name: 'Price Oracle Agent', description: 'Fetches and verifies crypto prices from multiple sources', capabilities: ['fetch-price', 'verify-price'], version: '1.0.0', owner: wallet.address }; // Upload metadata to IPFS (use nft.storage, pinata, etc.) // const metadataUri = 'ipfs://Qm...'; const tx = await identityRegistry.registerAgent(agentId, metadataUri); await tx.wait(); console.log(`Agent registered: ${agentId}`); ``` ### 3. Discover Other Agents ```typescript theme={null} // Find agents by capability const agentIds = await identityRegistry.getAgentsByCapability('execute-trade'); for (const id of agentIds) { const metadata = await identityRegistry.getAgentMetadata(id); const reputation = await reputationRegistry.getReputation(id); console.log({ id, name: metadata.name, score: reputation.score, successRate: `${reputation.successfulInteractions}/${reputation.totalInteractions}` }); } ``` ### 4. Interact & Build Reputation ```typescript theme={null} async function interactWithAgent(targetAgentId: string, action: () => Promise) { try { // Execute the interaction await action(); // Record success await reputationRegistry.recordInteraction(targetAgentId, true, 100); console.log('Interaction successful'); } catch (error) { // Record failure await reputationRegistry.recordInteraction(targetAgentId, false, 50); console.error('Interaction failed:', error); } } // Example: Trade with another agent await interactWithAgent(targetAgentId, async () => { // Your agent logic here console.log('Executing trade...'); }); ``` ## Core Concepts ### 1. Agent Identity Each agent registers an on-chain identity: ```typescript theme={null} interface AgentIdentity { id: bytes32; // Unique agent identifier owner: address; // Agent owner/creator metadata: string; // URI to agent details (name, description, capabilities) registeredAt: uint256; // Registration timestamp } ``` ### 2. Reputation Tracking Reputation accumulates through on-chain interactions: ```typescript theme={null} interface Reputation { agentId: bytes32; score: uint256; // Cumulative reputation score totalInteractions: uint256; successfulInteractions: uint256; lastUpdated: uint256; } ``` ### 3. Verification System Third parties can verify agent capabilities: ```typescript theme={null} interface Verification { agentId: bytes32; verifier: address; // Who verified claim: string; // What was verified (e.g., "can execute trades") validUntil: uint256; // Expiration } ``` ### A2A Interaction Flow ```mermaid theme={null} sequenceDiagram AgentA->>IdentityRegistry: Look up AgentB AgentA->>ReputationRegistry: Check AgentB reputation AgentA->>VerificationRegistry: Verify AgentB capabilities AgentA->>AgentB: Execute transaction AgentB->>ReputationRegistry: Report interaction result ``` ## API Reference ### Identity Registry | Function | Description | | ------------------------------------- | ------------------------ | | `registerAgent(agentId, metadataUri)` | Register a new agent | | `getAgentMetadata(agentId)` | Fetch agent metadata | | `getAgentsByOwner(address)` | List all agents by owner | | `updateMetadata(agentId, newUri)` | Update agent metadata | ### Reputation Registry | Function | Description | | --------------------------------------------- | ---------------------------- | | `recordInteraction(agentId, success, weight)` | Record an interaction result | | `getReputation(agentId)` | Get agent reputation data | | `getTopAgents(limit)` | Get highest-ranked agents | ### Verification Registry | Function | Description | | ------------------------------------------ | ----------------------------------- | | `verifyCapability(agentId, claim, expiry)` | Verify a capability | | `getVerifications(agentId)` | Get all verifications for agent | | `isVerified(agentId, claim)` | Check if specific claim is verified | ## Use Cases ### 1. Multi-Agent Workflows Orchestrate complex tasks across multiple autonomous agents: * **Research agents** gather data * **Analysis agents** process findings * **Execution agents** perform transactions Each agent discovers and evaluates others through the reputation layer. ### 2. Agent Marketplaces Build open markets where agents compete: * Agents with higher reputation command higher fees * Verification badges signal specialized capabilities * Performance tracked on-chain, transparent to all ### 3. Cross-Organization Collaboration Enable agents from different organizations to collaborate: * No pre-existing trust relationships required * Reputation and verification provide confidence * Identity registry ensures accountability ## Security Considerations 1. **Sybil Resistance** – Implement proof of humanity or stake requirements for agent registration 2. **Reputation Gaming** – Design scoring to prevent manipulation (e.g., decay scores over time) 3. **Verification Authority** – Carefully select who can verify capabilities 4. **Metadata Integrity** – Use content-addressed storage (IPFS) with hashes stored on-chain ## Current Status ERC-8004 is an active proposal in the Ethereum ecosystem. The standard is being refined through community discussion. Implementation details may evolve. ## Next Steps Create autonomous agents on SKALE Build payment-enabled agents using x402 ## Resources * [ERC-8004 Proposal](https://eips.ethereum.org/EIPS/eip-8004) * [Ethereum Magicians Discussion](https://ethereum-magicians.org/t/erc-8004-trustless-agents/) * [Agent Security Best Practices](/developers/security) * [SKALE Agent Documentation](/cookbook/agents/build-an-agent) # Start with x402 Source: https://docs.skale.space/get-started/agentic-builders/start-with-x402 Build payment-enabled agents using the x402 protocol on SKALE x402 is an internet-native payments protocol that enables agents to send and receive payments for services. This helps developers build autonomous agents and agentic applications directly on SKALE. This guide covers the essential concepts for building agents that interact with x402 payments. ## What is x402? x402 is a payment protocol that extends HTTP with native payment capabilities using the `402 Payment Required` status code. It enables: * **Paywalled Resources**: Protect APIs and services behind payments * **Autonomous Payments**: Agents can automatically pay for and access resources * **ERC-3009 Compatibility**: Uses `TransferWithAuthorization` for gasless payments * **Multi-Token Support**: Accept payments in various ERC-20 tokens ## Core Components ### 1. Facilitator A facilitator processes x402 payments by exposing endpoints that verify and settle payment authorizations: * `/verify` - Validates payment signatures without executing on-chain * `/settle` - Executes the on-chain payment settlement Facilitators generally support a 3rd endpoint -- `/supported` -- which allows agents to discover resources. **Learn More**: [Run a Facilitator](/cookbook/x402/facilitator) ### 2. Payment Middleware Middleware that protects HTTP endpoints by enforcing the x402 payment handshake: * Returns `402 Payment Required` when payment is missing * Forwards payment to facilitator for verification * Allows request to proceed after successful settlement The middleware is implemented at the seller/merchant level and is able to be fully stateless. This is done through the use of a 3rd party facilitator who handles the above verification and settlement on your behalf. **Learn More**: [Accept Payments](/cookbook/x402/accepting-payments) ### 3. Payment Client Client libraries that handle the x402 payment flow automatically: * Detect `402 Payment Required` responses * Create and sign payment authorizations * Retry requests with proper payment headers **Learn More**: [Make Payments](/cookbook/x402/buying) ## Quick Start: Build a Payment-Enabled Agent Here's a minimal example of an agent that can access paywalled resources: ```typescript theme={null} import { x402Client, x402HTTPClient } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { privateKeyToAccount } from "viem/accounts"; // Setup wallet const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`); // Create x402 client const evmScheme = new ExactEvmScheme(account); const coreClient = new x402Client().register("eip155:*", evmScheme); const httpClient = new x402HTTPClient(coreClient); // Access paywalled resource async function accessResource(url: string) { try { const response = await fetch(url); if (response.status === 402) { // Get payment requirements const responseBody = await response.json(); const paymentRequired = httpClient.getPaymentRequiredResponse( (name) => response.headers.get(name), responseBody ); // Create payment payload const paymentPayload = await httpClient.createPaymentPayload(paymentRequired); const paymentHeaders = httpClient.encodePaymentSignatureHeader(paymentPayload); // Retry with payment const paidResponse = await fetch(url, { headers: { ...paymentHeaders } }); if (!paidResponse.ok) { throw new Error(`Request failed: ${paidResponse.status}`); } return paidResponse.json(); } if (!response.ok) { throw new Error(`Request failed: ${response.status}`); } return response.json(); } catch (error) { console.error("Error accessing resource:", error); throw error; } } ``` ## Use Cases ### Autonomous Service Access Build agents that pay for API calls, data feeds, or AI services: ```typescript theme={null} // Agent pays for weather data automatically const weatherData = await agent.accessResource("https://api.example.com/weather"); ``` ### Monetize Agent Services Create services where agents pay to access your AI models or data: ```typescript theme={null} // Protect your AI endpoint with x402 app.get("/ai/analyze", paymentMiddleware, async (c) => { const result = await model.analyze(c.req.query); return c.json(result); }); ``` ### Multi-Agent Economies Enable agents to pay each other for services in a decentralized economy: ```typescript theme={null} // Agent A requests work from Agent B, pays automatically const result = await agentA.requestWork(agentB.endpoint, taskData); ``` ## Payment Tokens on SKALE SKALE Base Sepolia Testnet supports various tokens for x402 payments: | Token | Address | Decimals | | ------------ | -------------------------------------------- | -------- | | Axios USD | `0x61a26022927096f444994dA1e53F0FD9487EAfcf` | 6 | | Bridged USDC | `0x2e08028E3C4c2356572E096d8EF835cD5C6030bD` | 6 | ## Next Steps Create autonomous agents that handle x402 payments Protect your APIs with x402 payment middleware Implement payment client for accessing paywalled resources Set up your own payment processing service ## Resources * [x402 Protocol Specification](https://x402.org) * [Coinbase x402 SDK](https://github.com/coinbase/x402) * [ERC-3009 Standard](https://eips.ethereum.org/EIPS/eip-3009) * [x402 Examples Repository](https://github.com/TheGreatAxios/x402-examples) # BITE Sandbox Source: https://docs.skale.space/get-started/quick-start/bite-sandbox BITE Sandbox Quick Start Guide Need sFUEL, USDC, or access to the private developer group? Join us in [Discord](https://discord.gg/skale) and open a support ticket. ## Introduction Autonomous agents can now discover each other, communicate, and move value — **for real**. This sandbox is a space to explore agent-to-agent commerce: agents paying each other, discovering services, negotiating terms, and settling value without constant human involvement. ## Chain Details ### BITE V2 Sandbox 2 | Name | Value | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | RPC | `https://base-sepolia-testnet.skalenodes.com/v1/bite-v2-sandbox` | | Explorer | [Blockscout](https://base-sepolia-testnet-explorer.skalenodes.com:10032) | | Chain ID | `103698795` (Hex: `0x62e516b`) | | Tokens & Contracts | USDC: [0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8](https://base-sepolia-testnet-explorer.skalenodes.com:10032/address/0xc4083B1E81ceb461Ccef3FDa8A9F24F0d764B6D8) | | Features | **x402** Facilitator, Encrypted Transactions (**BITE Phase 1 & 2**) | | BITE V2 | [Documentation](/developers/bite-protocol/conditional-transactions) | | BITE Demo | [Cookbook](/cookbook/privacy/conditional-transactions#rock-paper-scissors-with-bite-v2) | | Facilitator | Kobaru: [https://gateway.kobaru.io](https://gateway.kobaru.io) | You can also built on **SKALE Base Sepolia Testnet** if you are looking for access to Base Sepolia or more facilitators. For chain details go [here](/get-started/quick-start/skale-on-base#skale-base-testnet). *** ## FAQ & Important Developer Information **Q: Why is my contract failing to deploy/Out of Gas?** Solidity contracts should be compiled and deployed with version Shanghai or lower on SKALE Base. If you are using SKALE's BITE Sandbox, deploy contracts with EVM Version Istanbul. **Q: Need to add SKALE to your Wallet?** Go [here](https://base-sepolia.skalenodes.com/chains/bite-v2-sandbox) for the SKALE BITE Sandbox chain or [here](https://base-sepolia.skalenodes.com/chains/base-testnet) for SKALE's long running SKALE Base Sepolia testnet. Connect your wallet and then click "Connect to Chain" and it will automatically add the chain to your wallet for you. ## Resources | Resource | What It Does | Link | | -------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | **x402** | HTTP-native payments for APIs and services | [Start with x402](/get-started/agentic-builders/start-with-x402) | | **BITE** | Transaction encryption protocol developed by the SKALE Team | [BITE Protocol Intro](/concepts/bite-protocol/intro-bite-protocol) | | **Facilitators** | Off-chain entities handling payment routing | [Available facilitators](/get-started/agentic-builders/facilitators) | | **Encrypted Transactions** | Send encrypted transactions with built-in MEV protection (V1 = consensus-level, V2 = smart contract-level) | [TypeScript SDK](/developers/bite-protocol/typescript-sdk) | | **Google AP2 / A2A** | Agent-to-agent communication and protocols | [Google A2A](https://a2a-protocol.org/latest/) | # Get Testnet Tokens Source: https://docs.skale.space/get-started/quick-start/get-testnet-tokens How to get testnet tokens This section walks you through the steps to obtain testnet tokens and bridge them into a specific SKALE Chain for testing and development. ### SKALE Base Sepolia Testnet 1. Go to [Circle Faucet](https://faucet.circle.com/) 2. Select the following options: * Token: **USDC** * Network: **Base Sepolia** * Send to: **0x\_your\_wallet\_address** Circle Faucet 3. Head to the SKALE Base Portal -- [bridge section](https://base-sepolia.skalenodes.com/bridge?from=mainnet\&to=jubilant-horrible-ancha\&token=usdc\&type=erc20) 4. \[OPTIONAL] Check [here](https://docs.base.org/base-chain/tools/network-faucets) to get some gas to complete the bridge operation. 5. Enter the amount and select bridge SKALE Portal Bridge ### Next Steps Check the SKALE on Base for more chain details Check the SKALE on Ethereum for more chain details # Go Live Source: https://docs.skale.space/get-started/quick-start/go-live Launch your application and go live on SKALE The go-live process on SKALE can differ slightly from other chains and varies slightly depending on what product within the SKALE Ecosystem you are choosing to use. This guide focuses on deployment to the shared SKALE ecosystem chains like **SKALE Base** and one of the world's top gaming blockchains, **Nebula Gaming Hub** on SKALE Ethereum deployment. ## Deploying to SKALE Base The following is for SKALE Base chains (testnet and mainnet) which are **permissionless**. To deploy contracts and execute transactions on the SKALE Base Mainnet you need CREDITs. * Please check your CREDITS Balance [here](https://base.skalenodes.com/credits) CREDITS Menu * Select the button `Buy credits` above, select the payment method - `USDC` **or** `SKL` - and complete the purchase. Buy CREDITS * If any issues are faced please contact the [SKALE Team](https://discord.gg/skale) * Deploy you contracts on the SKALE Base. The chain details (ex: RPC, Chain Id) can be found [here](./skale-on-base). * Make sure to have enough CREDITS to deploy contracts as well as perform the necessary app specific transactions. * Head to the [SKALE Base Sepolia faucet](https://base-sepolia-faucet.skale.space) to get compute CREDITS. * If required a bigger CREDITS allocation (ex: it's required to run some dApp specific tests) please open a ticket on [SKALE Discord](https://discord.gg/skale) and request it to the SKALE Team. * Deploy you contracts on the SKALE Base Sepolia Testnet. The chain details (ex: RPC, ChainId) can be found [here](./skale-on-base). * The cookbook section includes examples showing how to deploy contracts using [Foundry](../../cookbook/deployment/setup-foundry) and [Hardhat](../../cookbook/deployment/setup-hardhat). * Check Solidity smart contract versions and EVM compiler. SKALE currently supports Solidity versions up to 0.8.24 and EVM Compiler versions up to Shanghai. ## Deploying to SKALE Ethereum The following is for Calypso, Europa, and Nebula Hubs. If deploying your own sChain on top of Ethereum the SKALE Developer Success team will provide you with an alternative list of checks for your dApps. * Current SKALE Ethereum Hub Chains do **NOT** have any of the standard factory contracts. If you need them, deploy to [SKALE Base](/get-started/quick-start/skale-on-base) instead. * If you have a smart contract that deploys other smart contracts (i.e a factory), please flag this contract during your review with the SKALE Team so they can help ensure it's properly allowlisted to deploy contracts in the future. * SKALE Ethereum Chains use sFUEL -- a valueless gas token -- which is distributed by dApps to users OR consumed through a Proof of Work mechanism that allows transaction gas to be "mined" on the fly using client compute. This means that `msg.value` can **NOT** be used in Solidity smart contracts for payments as it carries no value * Check Solidity smart contract versions and EVM compiler. SKALE currently supports Solidity versions up to 0.8.24 and EVM Compiler versions up to Shanghai. * Once your application is ready, please deploy your smart contracts to the testnet chain of your choice. See [SKALE on Ethereum](/get-started/quick-start/skale-on-ethereum) for chain options. * Deploy the remainder of your application -- frontend, APIs, agents -- and prepare to share this information with the SKALE Team for review (see below) * If you are in a dedicated channel with the SKALE Team, request a review with a member of the Developer Sucess team # SKALE on Base Source: https://docs.skale.space/get-started/quick-start/skale-on-base Bringing the power of SKALE to the Base ecosystem The first SKALE Chain is publicly available on [testnet](#skale-base-testnet) and in private beta on [mainnet](#skale-base-mainnet) ## Key Features The SKALE on Base testnet deployment retains 100% of the features you know and love from SKALE including: * **Privacy**: Encrypted transactions brings the privacy you need to the Base ecosystem * **Instant Finality**: No forks, no reorgs, and no multi-confirmation waits * **Zero Gas Fees**: Stop paying variable gas fees and get predicable costs with pre-paid compute credits * **Native Bridge**: Seamlessly transfer ERC-20, ERC-721, and ERC-1155 tokens between Base and SKALE without 3rd party bridges * **Solidity-native RNG**: No 3rd-party services or oracles needed generate random numbers onchain instantly (no callbacks needed) If you are looking to host your agent card with SKALE File Storage or have your own dedicated blockchain, join the team in [Discord](https://discord.gg/skale) to inquire. ## SKALE Base Mainnet | Name | Value | | --------------------- | ------------------------------------------------------------------------------------------ | | Name | SKALE Base | | RPC Url | [https://skale-base.skalenodes.com/v1/base](https://skale-base.skalenodes.com/v1/base) | | WSS Url | wss\://skale-base.skalenodes.com/v1/ws/base | | Explorer | [https://skale-base-explorer.skalenodes.com/](https://skale-base-explorer.skalenodes.com/) | | Native Token | CREDIT | | Native Token Decimals | 18 | | Chain Id | 1187947933 | | Chain Id Hex | 0x46cea59d | | Portal | [https://base.skalenodes.com/chains/base](https://base.skalenodes.com/chains/base) | ### SKALE Base Mainnet Tokens | Name | Symbol | Decimals | Base Address | SKALE Base Address | | ------------- | ------ | -------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | | USDC | USDC.e | 6 | [0x833...913](https://basescan.org/token/0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) | [0x858...b20](https://skale-base-explorer.skalenodes.com/token/0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20) | | Tether USD | USDT | 6 | [0xfde...bb2](https://basescan.org/token/0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2) | [0x2bF...fCa](https://skale-base-explorer.skalenodes.com/token/0x2bF5bF154b515EaA82C31a65ec11554fF5aF7fCA) | | Wrapped BTC | WBTC | 8 | [0x055...B9c](https://basescan.org/token/0x0555E30da8f98308EdB960aa94C0Db47230d2B9c) | [0x1ae...90e](https://skale-base-explorer.skalenodes.com/token/0x1aeeCFE5454c83B42D8A316246CAc9739E7f690e) | | Wrapped Ether | WETH | 18 | [0x420...006](https://basescan.org/token/0x4200000000000000000000000000000000000006) | [0x7bD...486](https://skale-base-explorer.skalenodes.com/token/0x7bD39ABBd0Dd13103542cAe3276C7fA332bCA486) | | ETH (ERC-20) | ETHC | 18 | Native ETH (Gas) on Base | [0xD2Aaa007...000](https://skale-base-explorer.skalenodes.com/address/0xD2Aaa00700000000000000000000000000000000) | ### SKALE Base Mainnet Deployed Contracts | Contract | Address | Description | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | Permit2 | [0x000000000022D473030F116dDEE9F6B43aC78BA3](https://skale-base-explorer.skalenodes.com/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) | Uniswap's signature-based approval system for ERC20 tokens | ## SKALE Base Testnet | Name | Value | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | Name | SKALE Base Sepolia | | RPC Url | [https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha](https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha) | | Explorer | [https://base-sepolia-testnet-explorer.skalenodes.com/](https://base-sepolia-testnet-explorer.skalenodes.com/) | | Native Token | CREDIT | | Native Token Decimals | 18 | | Chain Id | 324705682 | | Chain Id Hex | 0x135A9D92 | | Portal | [https://base-sepolia.skalenodes.com](https://base-sepolia.skalenodes.com) | | Faucet | [https://base-sepolia-faucet.skale.space](https://base-sepolia-faucet.skale.space) | ### SKALE Base Sepolia Testnet Tokens | Name | Symbol | Decimals | Base Sepolia Address | SKALE Base Sepolia Testnet Address | | ------------- | ------ | -------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | SKALE | SKL | 18 | [0xC20...2cF](https://sepolia.basescan.org/token/0xC20874EB2D51e4e61bBC07a4E7CA1358F449A2cF) | [0xaf2...57b](https://base-sepolia-testnet-explorer.skalenodes.com/token/0xaf2e0ff5b5f51553fdb34ce7f04a6c3201cee57b) | | USDC | USDC.e | 6 | [0x036...F7e](https://sepolia.basescan.org/token/0x036CbD53842c5426634e7929541eC2318f3dCF7e) | [0x2e0...0bD](https://base-sepolia-testnet-explorer.skalenodes.com/token/0x2e08028E3C4c2356572E096d8EF835cD5C6030bD) | | Tether USD | USDT | 6 | [0x0ed...4a8](https://sepolia.basescan.org/token/0x0eda7df37785f570560dA74ceCFD435AB60D84a8) | [0x3ca...0bf](https://base-sepolia-testnet-explorer.skalenodes.com/token/0x3ca0a49f511c2c89c4dcbbf1731120d8919050bf) | | Wrapped BTC | WBTC | 8 | [0xC38...8Bc](https://sepolia.basescan.org/token/0xC3893AEC98b41c198A11AcD9db17688D858588Bc) | [0x451...e87](https://base-sepolia-testnet-explorer.skalenodes.com/token/0x4512eacd4186b025186e1cf6cc0d89497c530e87) | | Wrapped Ether | WETH | 18 | [0x420...006](https://sepolia.basescan.org/token/0x4200000000000000000000000000000000000006) | [0xf94...fc0](https://base-sepolia-testnet-explorer.skalenodes.com/token/0xf94056bd7f6965db3757e1b145f200b7346b4fc0) | | ETH (ERC-20) | ETHC | 18 | Native ETH (Gas) on Base | [0xD2Aaa007...000](https://base-sepolia-testnet-explorer.skalenodes.com/address/0xD2Aaa00700000000000000000000000000000000) | ### SKALE Base Sepolia Testnet Deployed Contracts | Contract | Address | Description | | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | | Permit2 | [0x000000000022D473030F116dDEE9F6B43aC78BA3](https://base-sepolia-testnet-explorer.skalenodes.com/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) | Uniswap's signature-based approval system for ERC20 tokens | ### Chain Notes * When bridging ETH from Base Sepolia to the SKALE Chain, the ETH is locked as native ETH (msg.value) and is minted as an ERC-20 called ETHC on the SKALE Chain. On the frontend you can show it as ETH. * WETH and ETHC are both ERC-20 on the SKALE Chain but are deposited differently. WETH is deposited as WETH on Base. * This testnet chain is 100% permissionless. * CREATE2Factory, SingletonFactory, Multicall3, and Permit2 are deployed to their canonical addresses on this chain Join us in [Telegram](https://t.me/+o_7DCw9qcbI2NDYx) and ask for additional tokens. This chain and the network deployment are experimental and the following is subject to change. # SKALE on Ethereum Source: https://docs.skale.space/get-started/quick-start/skale-on-ethereum Access the power of Ethereum and it's ecosystem without the costs and restrictions ## Key Features SKALE on Ethereum testnet offers a variety of features at the chain level such as instant finality, zero gas fees, a native bridge to/from Ethereum, huge block gas limits, and a bespoke high-performance C++ EVM implementation. Additionally, this network boasts \~800 validator nodes run by \~100 SKALE Supernodes powering the multichain network. Interested in privacy features with the power of SKALE? Checkout [SKALE on Base](/get-started/quick-start/skale-on-base) to try out [BITE Protocol](/concepts/bite-protocol/intro-bite-protocol). ## Chains | Chain Name | Testnet Chain Details | Mainnet Chain Details | | -------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | | Calypso | [https://testnet.portal.skale.space/chains/calypso](https://testnet.portal.skale.space/chains/calypso) | [https://portal.skale.space/chains/calypso](https://portal.skale.space/chains/calypso) | | Europa Liquidity Hub | [https://testnet.portal.skale.space/chains/europa](https://testnet.portal.skale.space/chains/europa) | [https://portal.skale.space/chains/europa](https://portal.skale.space/chains/europa) | | Nebula Gaming Hub | [https://testnet.portal.skale.space/chains/nebula](https://testnet.portal.skale.space/chains/nebula) | [https://portal.skale.space/chains/nebula](https://portal.skale.space/chains/nebula) | > Gas Station for these chains can be found at [https://sfuelstation.com](https://sfuelstation.com) # Governance Source: https://docs.skale.space/get-started/resources/governance # Official Links Source: https://docs.skale.space/get-started/resources/official-links Connect with the SKALE community ## Community Resources ### Discord Join the SKALE community on Discord for real-time discussions, support, and updates. [Join Discord](https://discord.gg/skale) ### GitHub Explore SKALE's open-source codebase and contribute to the project. [GitHub](https://github.com/skalenetwork) ### Twitter/X Follow SKALE for the latest news and updates. [Follow on X](https://x.com/skalenetwork) ### Telegram Join the SKALE Telegram community. [Join Telegram](https://t.me/skaleofficial) ### Forum Participate in governance discussions and proposals. [SKALE Forum](https://forum.skale.network) ### Website Visit the official SKALE website for more information. [SKALE Website](https://skale.space) # SKALE Source: https://docs.skale.space/get-started/skale Purpose-built for the Internet of Agents ### **Start Building** Build and deploy your application in minutes on SKALE Deploy your own private SKALE Chain with full customization, dedicated resources, and enterprise-grade privacy. Build and deploy your agent in minutes on SKALE Hubs with zero gas fees. Accept x402 payments and sell your products and services on SKALE. Explore [developer guides](/cookbook/agents/build-an-agent) and [use cases](/get-started/resources/official-links). ### **Get Funded** Apply for SKALE grants to accelerate your project development and innovation. Connect with ecosystem builders, receive techncial support, and collaborate with others in the SKALE Ecosystem. ### **Stakers and Validators** Stake SKL tokens to secure the SKALE Network and recieve bounties for your economic collateral. Learn how to run a SKALE node and become a SKALE Validator. > Have questions? Join the SKALE team in [Discord](https://discord.gg/skale) for support! # The SKL Token Source: https://docs.skale.space/get-started/skl-token/skl-token A conceptual overview and introduction into the SKL Token The SKL token is the utility token of the SKALE Network. It is deployed on Ethereum as an ERC-777 hybrid utility and supports the following functions: * Allows delegation of SKL tokens to validators who then stake the tokens into a node to provide economical collateral for the Proof of Stake (PoS) network. * Validators stake SKL tokens run and secure SKALE Supernodes * Pay for SKALE Chains in accordance with SKALE Network's economic model * Participate in SKALE Network governance via the SKALE DAO for those who are actively delegated to a validator SKL is NOT the native gas token of SKALE Chains. Unlike other chains where the native token holds economic value in order to pay for gas fees, SKALE Chains use a token with no economic value called sFUEL which enables zero-gas cost transactions. The SKL token is activley bridged to SKALE Chains in ERC-20 format. The SKL token is native to Ethereum, but available across multiple chains. The following are the official deployments: | Chain | Address | Notes | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | | Ethereum | [https://etherscan.io/address/0x00c83aeCC790e8a4453e5dD3B0B4b3680501a7A7](https://etherscan.io/address/0x00c83aeCC790e8a4453e5dD3B0B4b3680501a7A7) | This is the original deployed token | | SKALE Europa | [https://elated-tan-skat.explorer.mainnet.skalenodes.com/token/0xE0595a049d02b7674572b0d59cd4880Db60EDC50](https://elated-tan-skat.explorer.mainnet.skalenodes.com/token/0xE0595a049d02b7674572b0d59cd4880Db60EDC50) | ERC-20 bridged in from Ethereum | | SKALE Nebula | [https://green-giddy-denebola.explorer.mainnet.skalenodes.com/address/0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d](https://green-giddy-denebola.explorer.mainnet.skalenodes.com/address/0x7F73B66d4e6e67bCdeaF277b9962addcDabBFC4d) | ERC-20 bridge in from Europa | | SKALE Calypso | [https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com/address/0x4048C4dd6eccF1Dc23b068211fDf20AD19602e50](https://honorable-steel-rasalhague.explorer.mainnet.skalenodes.com/address/0x4048C4dd6eccF1Dc23b068211fDf20AD19602e50) | ERC-20 Bridge in from Europa | ## Governance SKL delegators/stakers are elligible to bring items up for vote and vote on governance proposals via the SKALE DAO that impact the economic operations of the SKALE Network. This includes proposals for SKALE Chain pricing, network inflation, and more. See [the forum post introducing governance](https://forum.skale.network/t/a-proposal-for-skale-networks-on-chain-governance-system/447) for more. When you vote on a proposal you use your SKL tokens to signal approval or opposition of the active proposal up for vote. The more SKL tokens you have, the more influence you can vote with. * [Staking SKL](/get-started/skl-token/staking-skl) - Learn about staking SKL tokens * [Governance](https://forum.skale.network/t/a-proposal-for-skale-networks-on-chain-governance-system/447) - Participate in SKALE governance discussions # Staking SKL Source: https://docs.skale.space/get-started/skl-token/staking-skl Mechanics of delegating and staking SKL tokens on the SKALE Network ## Stake SKL Token To stake your SKL tokens, visit the [SKALE Portal Staking Page](https://portal.skale.space/staking). Connect the wallet of your choice, select a validator, and stake! All staking is currently conducted on the Ethereum Mainnet for SKALE on Ethereum. SKALE Expand chains do not have staking as this time. ### The Staking Lifecycle | Step | Action | Delegation Status | | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | | 1 | Delegate SKL Tokens to a Validator | Pending Acceptance | | 2 | Wait for validator to accept delegation | Pending | | 3 | Validator accepts delegation | Delegation Accepted | | 4 | Delegation goes into affect as stake on the 1st of the next month | Active | | 5 | Stake is locked for a 2 month period and auto re-stakes every 2 months unless undelegated. Rewards are distributed monthly based on performance and stake weight. | Active | | 6 | Undelegation request occurs -> moves to pending undelegation | Pending Undelegation | | 7 | Tokens are unlocked and able to be transferred after a 2 month period completes | Undelegation Completed | ## FAQ ## Why should I stake SKL tokens? Staking SKL tokens provides a token holder the following: * SKL Bounties -- rewards in return for providing security in the form of economic collateral to the SKALE Network * Governance Rights -- SKL delegators are elligible to open governance votes and vote on active proposals ### Is SKALE Proof-of-Stake (PoS)? Yes, the SKALE Network as a whole runs on a Delegated Proof of Stake (DPoS) system which is very common in Proof-of-Stake blockchains and allows token holders to delegate their tokens to valdiators who stake on their behalf. ### What is delegation? Delegation is the active of delegating i.e giving the underlying power to a validator who can then stake on youre behalf. Many networks including SKALE allow delegation so that token holders are not required to run a validator node themselves or participate in DeFi in order to secure the network. ### What is staking? Staking is the act of locking up SKL tokens for a given period of time as economic collateral. Creating a SKALE Network supernode requires SKL tokens to be staked into the network in order to operate. Validators in the network stake the SKL tokens they receive from delegators. ### What is the default staking period? The default staking period is 2 months. There are no other staking periods available at this time. ### How long is the undelegation/unboding period? The amount of time it takes for an undelegation request to be fully processed and the tokens be unlocked and available for transfer is the staking period -- 2 months by default -- minus the amount of time remaining in the active period. ### Where do tokens go when delegated? SKALE uses the ERC-777 token standard which allows for tokens to remain within the delegators wallet throughout the staking lifecycle. This means that the tokens are NEVER transferred from the delegators wallet. During the delegation process, if you see tokens moving from your wallet to another, stop immediatley and contact the SKALE team. ### When can I undelegate my tokens? You can undelegate your tokens at any time except for within the last 3 days of the month. This is a security measure to prevent significant volatility in stake weight for a validator before the next epoch starts. ### What is the minimum delegation amount? This is set by the validator organization and can be found on the validator's page on the [SKALE Portal](https://portal.skale.space/validators). # Compliance Requirements Source: https://docs.skale.space/validators/compliance-requirements Requirements for Validator Compliance The following document outlines compliance requirements for SKALE Network supernodes. Failure to follow these requirements will result in a supernode forfeiting bounty for each month that it's not in compliance as well as SKALE Chain payment claims. Please be sure to follow the entire proper setup and backup procedures described in Validator Denali Upgrade documentation to ensure your supernode is operating as intended. ## Compliance Requirements The following compliance requirements are required to ensure all supernodes meet a standard of performance. Failure to meet ANY ONE of the following Denali compliance requirements will result in a supernode being marked "Out of Compliance" by the NODE Foundation and the supernode may be forced to forfeit monthly bounties if it is unable to return to "In Compliance" by the time the monthly `getBounty` is called. Compliance requirements may be reviewed and modified at any given time. ## General Check Supernode operators can use this general PASS/FAIL check to confirm whether a supernode meets the compliance requirements. [https://dune.com/skale/metrics](https://dune.com/skale/metrics) ## Wallet Balances * At least 0.5 ETH in self-recharging validator wallet (should be >= NODES\_NUMBER \* 0.5) * At least 1 ETH in each supernode wallet ## Hardware * A Linux x86\_84 machine * At least 8 physical cores (benchmark AWS t2.2xlarge) * At least 32 GB RAM * At least 100 GB root storage (not tested at the moment, recommended) * At minimum, separate not mounted block device - 2 Tb TB (1.9 TB actual measure) attached and unformatted non-boot storage (Note that separate not mounted block device - 2Tb TB storage results in \< separate not mounted block device - 2Tb TB actual physical storage) * At least 16 GB swap Test: `http://YOUR_SKALE_NODE_IP:3009/status/hardware` Example response: ```shell theme={null} {"data": {"cpu_total_cores": 8, "cpu_physical_cores": 8, "memory": 33675792384, "swap": 67350032384, "system_release": "Linux-5.4.0-1045-aws", "uname_version": "#47~18.04.1-Ubuntu SMP Tue Apr 13 15:58:14 UTC 2021", "attached_storage_size": 214748364800}, "error": null} ``` ## Networking * Non-expired SSL certificates for each supernode. * The certificate file should be issued in PEM format, issued by a trusted authority, and contain a full certificate chain. * Certificate should always be renewed to avoid expiry * Don't touch machine firewall (iptables installed by skale supernode software automatically sets machine firewall rules), external VPC or networking firewall ensure **some\_port\_range**. ## Servers ### SGXWallet * 1 SGX server for up to 5 SKALE supernodes (not tested, but required) * 6 cores (not tested, but required) * 8GB RAM (not tested, but required) Test: `http://YOUR_SKALE_NODE_IP:3009/status/sgx` Example response: ```shell theme={null} {"data": {"status": 0, "status_name": "CONNECTED", "sgx_wallet_version": "1.75.0"}, "error": null} ``` ### ETH Mainnet Endpoint After the merge a Mainnet supernode operator must run both an execution client and a consensus client at the same time. It is a requirement to run Geth as an execution client. We highly recommend using Prysm as a consensus client because it's the one which SKALE software is being tested on. You can find complete installation instructions [here](https://docs.prylabs.network/docs/install/install-with-script). Test: `http://YOUR_SKALE_NODE_IP:3009/status/endpoint` Example Response: ```shell theme={null} {"data": {"block_number": 8728517, "syncing": false, "trusted": true, "client": "Geth/v1.10.2-stable-c2d2f4ed/linux-amd64/go1.16"}, "error": null} ``` ## Software Latest Mainnet software versions are defined [here](https://github.com/skalenetwork/skale-network/tree/master/releases): * Supernode software versions must be updated to the latest Denali requirements * SGXWallet software versions must be updated to the latest Denali requirements * SGX container should be up and running and should be responding to health check request * FILEBEAT\_HOST should be defined in .env and the filebeat container should be up and running. The supernode must be sending logs to elastic server * All skale-containers are always running on the supernode * filebeat * skale-admin * bounty * transaction-manager * nginx * skale-api * celery * SKALE Chain (if selected for SKALE Chain) Test: `http://YOUR_SKALE_NODE_IP:3009/status/meta-info` Example response: ```shell theme={null} {"data": {"version": "1.1.0", "config_stream": "1.2.1", "docker_lvmpy_stream": "1.0.1-stable.1"}, "error": null} ``` Test: `http://YOUR_SKALE_NODE_IP:3009/status/SKALE Chain-containers-versions` Example response: ```shell theme={null} {"data": {"skaled_version": "3.5.12-stable.1", "ima_version": "1.0.0-develop.148"}, "error": null} ``` Test: `http://YOUR_SKALE_NODE_IP:3009/status/core` Example response: ```shell theme={null} {"data": [{"image": "skalenetwork/SKALE Chain:3.5.12-stable.1", "name": "skale_schain_beautiful-al-anz", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 21709, "ExitCode": 0, "Error": "", "StartedAt": "2021-04-26T14:49:51.705052477Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, ... ``` BTRFS kernel module must be enabled. `http://YOUR_SKALE_NODE_IP:3009/status/btrfs` returns information about btrfs kernel module (enabled/disabled). Public IP must be the same as the registered supernode IP. `http://YOUR_SKALE_NODE_IP:3009/status/public-ip` returns public ip address (source of the packets that your supernode is sending to other supernodes) IMA Container must pass healthcheck. `http://YOUR_SKALE_NODE_IP:3009/status/ima` returns information about basic ima healthcheck ## Networking * Check key-cert pair validity using `http://YOUR_SKALE_NODE_IP:3009/status/ssl` * Test ssl certificates using `skale ssl check` * Don't touch machine firewall (iptables installed by supernode software automatically sets machine firewall rules), external VPC or networking firewall ensure Ports open on **80, 311, 443,** 3009, 8080, 9100, 10000–18192, and ICMP IPv4 (in other words, not closed by an external firewall). # Validator FAQ Source: https://docs.skale.space/validators/faq Frequently Asked Quesitons related to SKALE Network Validators ### Are network incentives setup to be mining pool resistant? Yes -- SKALE is a Delegated Proof of Stake Network; meaning there are no direct mining capabilities. Additionally, the concept of pooling mining power has been eliminated on SKALE entirley where every Supernode has equal earning power simply by supplying compute to the network and meeting the decentralized SLA. To learn more checkout the article on the [SKALE Blog](https://skale.space/blog/eliminating-mining-pools) about Eliminating Mining Pools. ### Are you aware of Intel SGX-related attacks like Plundervolt, cache attacks, and other vulnerabilities? The SKALE team stays abreast of any and all security issues related to Intel SGX and takes all measures necessary to counteract them. Intel is very good at providing BIOS updates to address any security holes and prevent these types of attacks. The validator also shares security responsibility for ensuring that their service providers consistently use the latest BIOS version. The SKALE team will keep validators abreast of security issues, recommend best practices, and work closely with validators to address these issues and more. ### Why the specific requirements for the disk storage and memory? The SKALE Network is designed to be highly performant with high throughput and low latency. On-chain storage is also an optional offering by the network and so disk storage needs to be above a certain amount in order to support this capability. Nodes operate in a virtualized manner, meaning that one SKALE Supernode can service many chains. The network can support a variety of chain sizes where different chain sizes use some different fractional portion of a Supernode. The network currently operates with virtualized nodes consuming 1/8th of a Supernode. We should also mention the future that the SKALE Network is building towards is one where decentralized solutions will make use of a number of chains – not unlike the way cloud applications today make use of multiple microservice meshes and/or database clusters, each one optimized for the app features or capabilities it is supporting. ### Has the Intel SGX technology been adequately vetted? Yes, the SKALE technology team has analyzed the benefits and operation of the Intel SGX chipset in depth, is aware of any perceived vulnerabilities, and is satisfied the technology meets the security and data protection requirements of the network. ### Some networks that use BLS signatures use Ledger as opposed to Intel SGX. Why the difference? Most often the case is that these other networks are applying BLS multisignature aggregation. Simply put, every validator generates a BLS signature and then performs operations with this signature. The network protocol aggregates all signature operations into one. What you gain from this process is compressed data, since instead of multiple ECDSA keys, you have a single aggregated signature, and nothing more. This use is suitable for Ledger hardware. SKALE's process is drastically different. SKALE uses BLS threshold signatures – a different and much more complicated process than BLS signature aggregation. To make a threshold signature work – such that only **t of n** operators are needed to perform an operation – one needs a trusted process to construct a BLS public key based on secret shares from each of the operators. This is called DKG or distributed key generation and requires much more complicated processing than what Ledger-based solutions can provide. Whereas Ledger-based networks are simply storing a BLS signature for the purposes of multi-signature aggregation, Intel SGX is being used for DKG and related threshold operations. Another critical distinction is that BLS signature aggregation is performed per validator, whereas BLS threshold signature operations are done per SKALE Chain and on a frequent basis. If a validator node is supporting 128 SKALE chains, then the node core has to generate 128 BLS threshold public keys and 128 BLS threshold private keys via distributed key generation. If a validator is using a single Intel SGX server to support 16 nodes and each node is running 128 chains, then it needs to generate and maintain 2048 public and private BLS threshold keys. When a node is swapped into a new SKALE chain, the DKG process is re-initiated with all nodes within the chain group, resulting in new public and private key generation for that specific SKALE chain. Given this increased frequency of digital key generation and complexity of BLS threshold cryptography, the use of Intel SGX is an optimal solution for addressing the performance and data security requirements of the network. ### What are the hardware requirements for being a validator node? Validators need to provision and operate their own server or servers with sufficient network capacity and data center operational integrity. Servers can operate in a public or private cloud setting or on locally provisioned hardware, provided they meet SLA requirements. A particular requirement for servers is that they operate Intel SGX. Intel SGX (Software Guard Extensions) is a set of instructions that increases the security of application code and data, providing added protection from disclosure or modification. Developers can partition sensitive information into enclaves, which are areas of execution in memory with more security protection. It is recommended that all nodes in a validator set be Intel SGX servers although it’s also possible to set up a network nodes with a certain ratio of Intel SGX servers to non-SGX servers. Other node requirements include Ubuntu 22.04 or above, 200GB of attached storage, and 32GB RAM. ### What type of validators is the network looking for? The SKALE Network is open to any validator as long as they meet the technical requirements and staking commitment. Validators should have the desire and resources to run Supernodes as well as personnel capable of operating the SKALE Supernodes with proper global coverage. ### What is the Service Level Agreement (SLA) threshold? The SLA threshold is a high percentage uptime and low latency response time. If a validator cannot meet the SLA requirements for a particular epoch (as determined by the network monitoring procedures), they will not be eligible to participate in the awards from the bounty pool. ### Is there a minimum delegation requirement? No -- there is no minimum delegation requirement for a validator. Validators can chose to self-delegate or choose to bring on delegators, in which case, validators can set minimum delegation amount for accepting delegations. ### Does the network have support for validator commissions and delegation of investor stakes? Yes -- validators can raise stake from delegators and have this reflected via contracts running the network. Commissions can be set for the monthly bounties such that payouts can be split into both commissions (to the validator) and delegator awards (to stakers). The commission rate that is set is solely at the discretion of validators and on what the market will bear. ### Is there any unbonding or undelegation period? Yes -- the unbonding period for SKL tokens is the time between an undelegation request and the epoch at whicher the current delegation ends. Example: Delegator delegates 100,000 SKL to Validator 1 on January 5th. Validator 1 accepts delegation on January 25th. Delegation goes into affect February 1. The delegation remains in affecet and and will automatically re-delegate on April 1. If an undelegation request is made anytime between February 1 and April 1 (\~3 days before is the recommended deadline to request undelegation); then the unboding period is the time between undelegation request and April 1. ### Is there a Minimum Staking Requirement (MSR)? Why is there an MSR? Yes, the MSR for a single Supernode is currently **20,000,000 SKL**. A key requirement for an effective security and execution layer is a proper incentive structure that addresses both penalties and rewards. With respect to the former, every validator node should have significant value staked into a network. Staking is an enforcer of good behavior in that if a validator decides to collude or go byzantine and gets caught, it will lose its stake and be removed from the network. ### How does the network mitigate security risks? Security and validity of transactions in a chain or subchain in a second layer primarily rests with the performance and behavior of the validator nodes. To make sure the validation layer is operating properly, a network first has to have a large number of validator nodes. A small number of nodes in a network is inherently risky and fragile. In addition and as a requirement for a secure and robust network, it ideally needs to provide for: 1. The random selection of chain validator sets 2. The rotation of nodes in and out of chains on a frequent basis. Without randomness and rotation, there is a far greater risk of bribery of and/or collusion between validators, vastly reducing the security and integrity of the chains within a network. # Hardware Requirements Source: https://docs.skale.space/validators/hardware Hardware requirements for SKALE Network validators ## Hardware Requirements The following are the hardware requirements for running a SKALE Network validator node: * **Machine**: A Linux x86\_64 machine * **Physical Cores**: At least 8 physical cores * **RAM Size**: At least 32 GB RAM * **Attached Storage**: Separate not mounted block device - 2TB (1.9 TB actual measure) * **Swap Size**: At least 16 GB swap * **Port Range**: See networking requirements in compliance documentation For more detailed hardware requirements and compliance information, see [Compliance Requirements](/validators/compliance-requirements). # Migrate SKALE Node with SGXWallet to New Server Source: https://docs.skale.space/validators/migrate-node-with-sgxwallet Step-by-step guide for migrating a SKALE node and SGXWallet to a new server This guide provides instructions for migrating a SKALE node and SGXWallet to a new server following the recommended migration process. ## Prerequisites Before starting the migration, ensure you have: * Access to both the current and new servers * Administrative privileges on both systems * Secure network connection between servers Make sure backup is created before starting migration. You can use the `skale node backup .` command to create a backup of your current node and save `sgx_data` folder for SGX wallet as well as `sgxwallet_backup_key.txt` ## Migration Steps ### Migrate SGX 1. **Setup new SGX server** Prepare a new server with: * Ubuntu 22.04 LTS (Jammy Jellyfish) * SGX-enabled Intel processor * 8 physical cores * 16 GB RAM * 200 GB disk mounted as / * Swap size equals to half (1/2) of RAM size 2. **Install packages required for SGX** ```bash theme={null} sudo apt-get update && sudo apt-get upgrade -y sudo apt-get install -y docker.io docker-compose-plugin sudo apt-get install -y build-essential make cmake gcc g++ yasm python libprotobuf10 flex bison automake libtool texinfo libgcrypt20-dev libgnutls28-dev sudo apt-get install -y docker sudo apt-get install -y libelf-dev cpuid ``` 3. **Verify processor supports Intel SGX** ```shell theme={null} cpuid | grep SGX: ``` After installing docker make sure that `live-restore` option is enabled in `/etc/docker/daemon.json`. See more info in the [docker docs](https://docs.docker.com/config/containers/live-restore/). 4. **Disable automatic updates** It's recommended to only update the SGXWallet server if there are critical security fixes. This is because SGXWallet is based on low level technology, and kernel updates may break the system. Currently SGX is tested on 5.15\* kernels. It's best to avoid minor version updates too. To make sure `apt update` won't update the kernel you should use apt-mark hold command: ```shell theme={null} sudo apt-mark hold linux-generic linux-image-generic linux-headers-generic ``` Also if you configured unattended upgrades, you should make sure kernel won't update automatically. To do this, add the following lines to `/etc/apt/apt.conf.d/50unattended-upgrades` file: ```shell theme={null} Unattended-Upgrade::Package-Blacklist { "linux-generic"; "linux-image-generic"; "linux-headers-generic"; }; ``` **Output** ```shell theme={null} SGX: Software Guard Extensions supported = true ``` 5. **Configure firewall between SGX and node servers** 6. **Download SGXWallet source code** Clone SGX Wallet Repository to your machine: ```shell theme={null} git clone https://github.com/skalenetwork/sgxwallet/ cd sgxwallet git checkout stable ``` 7. **Enable SGX on the new server** SGX Wallet repository includes the sgx\_enable utility. To enable SGX run: ```shell theme={null} sudo ./sgx_enable ``` **Install SGX driver:** ```shell theme={null} cd scripts sudo ./sgx_linux_x64_driver_2.11.b6f5b4a.bin cd .. ``` **System Reboot:** Reboot your machine after driver install! **Check driver installation:** To check that isgx device is properly installed run this command: ```shell theme={null} ls /dev/isgx /dev/sg0 ``` If you don't see the isgx device, you need to troubleshoot your driver installation from [here](https://github.com/skalenetwork/sgxwallet/blob/develop/docs/enabling-sgx.md). 8. **Copy sgx\_data folder to the new server** From your old server, securely transfer the SGX data: ```bash theme={null} # On old server - create backup tar -czf sgx_data_backup.tar.gz -C sgxwallet/run_sgx sgx_data # Transfer to new server (use scp, rsync, or secure method) scp sgx_data_backup.tar.gz user@new-server:/path/to/transfer/ # On new server - extract to correct location cd sgxwallet/run_sgx tar -xzf /path/to/transfer/sgx_data_backup.tar.gz ``` After uploading backup to the new server make sure that sgxwallet\_backup\_key.txt is stored in sgx\_data 9. **Set the version and -b flag in docker-compose.yml** Edit the `sgxwallet/run_sgx/docker-compose.yml` file: * Change the image version to: `skalenetwork/sgxwallet:1.9.0-stable.4` * Add `-b` to the existing command section (don't replace the entire command, just add `-b` to it) 10. **Run docker-compose up -d** ```bash theme={null} cd sgxwallet/run_sgx docker-compose up -d # Verify SGXWallet is running docker-compose ps docker-compose logs ``` ### Migrate Node 1. **Setup new node server** Prepare a new server with: * Ubuntu 22.04 LTS (Jammy Jellyfish) * 8 physical cores * 32 GB RAM * 100 GB disk mounted as / * 2Tb additional disk (not mounted) 2. **Install packages required for the node** ```bash theme={null} sudo apt-get update && sudo apt-get upgrade -y sudo apt-get install -y docker.io docker-compose-plugin nftables ``` After installing docker make sure that `live-restore` option is enabled in `/etc/docker/daemon.json`. See more info in the [docker docs](https://docs.docker.com/config/containers/live-restore/). 3. **Disable automatic updates** Currently SKALE Node is tested on 5.15\* kernels. It's best to avoid minor version updates too. To make sure `apt update` won't update the kernel you should use apt-mark hold command: ```shell theme={null} sudo apt-mark hold linux-generic linux-image-generic linux-headers-generic ``` Also if you configured unattended upgrades, you should make sure kernel won't update automatically. To do this, add the following lines to `/etc/apt/apt.conf.d/50unattended-upgrades` file: ```shell theme={null} Unattended-Upgrade::Package-Blacklist { "linux-generic"; "linux-image-generic"; "linux-headers-generic"; }; ``` 4. **Run node backup on old server** ```bash theme={null} # On old server skale node backup . ``` This will create a backup tarball (e.g., `skale-node-backup-YYYY-MM-DD-HHMMSS.tar.gz`) 5. **Copy backup tarball and .env config to the new server** ```bash theme={null} # Transfer backup and configuration files scp skale-node-backup-*.tar.gz user@new-server:/path/to/new/location/ scp .env user@new-server:/path/to/new/location/ ``` 6. **Download node-CLI version** ```bash theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.6.2/skale-2.6.2-Linux-x86_64 > /usr/local/bin/skale chmod +x /usr/local/bin/skale # Verify installation skale --version ``` 7. **Restore node from backup** ```bash theme={null} # Navigate to your working directory cd backup tarball location # Restore node using backup and environment file skale node restore skale-node-backup-*.tar.gz .env ``` Keep the old server running until you've fully verified the new setup is working correctly. This allows for quick rollback if needed. # Changing Node IP Source: https://docs.skale.space/validators/node-cli/changing-node-ip Explores how to handle management of IP addresses with SKALE Nodes. You can't change your node IP address at this time. What is possible is to remove the node with the incorrect/old IP address and register a new node under the correct/new IP while keeping the old SGX key. Please contact the core team before proceeding for further guidance. It is recommended to use a **permanent IP that can dynamically point to a node's machine IP (for example, elastic IP)** so you can update machines if needed without having to change the IP recorded in SKALE Manager during node registration. If you must change your node IP in SKALE Manager, you must perform a node exit and re-registration between the time after getBounty is called and before 72 hours (3 days) elapses in the next month. Also, performing a node exit could be expensive to the node operator if it's currently supporting SKALE Chains. To perform a node exit, and register a new machine and IP: 1. Be sure to backup SKALE Node and SGX Wallet. 2. Be sure you have your SGX Backup key. 3. Execute `skale exit start` on the exiting node during the time window between getBounty and 72 hours from the UTC start of the month. 4. Verify the exit process by executing `skale exit status`. (You don't need to wait for exit to finalize) 5. Create a new node machine (`skale node init .env`, `skale wallet info`). 6. From the old machine, move your node address funds to the new node wallet address `skale wallet send [new_address] [amount]`. 7. Unlink the old node address `sk-val validator unlink-address [OLD_NODE_ADDRESS] ` 8. Link the new node to the validator ID `skale node signature [VALIDATOR_ID]`, `sk-val validator link-address [NEW_NODE_ADDRESS] [NEW_NODE_SIGNATURE]` 9. Register the node with under the new IP `skale node register --name [NODE_NAME] --ip [NODE_IP] --port [PORT]`. To perform a node exit, keep the original node machine and its data and update the IP, follow these steps (NOT RECOMMENDED): 1. Perform the general node exit procedure steps 1 and 2 above. 2. Be sure you have securely saved the sgx\_key\_name before moving to step 3. 3. Remove .skale directory, remove all old containers, volumes, etc 4. Do skale node init again. 5. Go to `~/.skale/node_data/node_config.json` and replace `sgx_key_name` field with the old one. 6. Link the node to the validator ID again. 7. Register node with a new IP. # Node SSL Setup Source: https://docs.skale.space/validators/node-cli/node-ssl-setup Document walks a validate through how to setup SSL for a node. ## Introduction Node SSL certificates support secure communication with SKALE Chain endpoints. By default, each node of a SKALE Chain listens on HTTP and WS ports. If SSL certs exist on the node, then HTTPS and WSS ports are also turned on. Node SSL certificates aren't required to start a node, register it, or create SKALE Chains. However, dApp developers using SKALE Chains need HTTPS/WSS endpoints to work with toolings such as API based wallets and other integrations. Therefore, SSL certificates are required for SKALE Network validator nodes. ## Node SSL onboarding Here's an example of a step-by-step process of setting up validator node SSL. ### Prerequisites To upload SSL certificates to the node, you’ll have to do a few steps on your side which may vary depending on your domain name provider, certificate authority, etc. [See here for reference example](#reference-letsencrypt-tutorial) of how use free Let's Encrypt certificates. The steps include: * getting domain names for each node. * issuing SSL certificates for them. * uploading certificates to the node. * setting up domain name for the node in the SKALE Manager smart contracts. This procedure will vary depending on your domain management service (Google Domains, GoDaddy, etc). 1. Domain name and redirects The first thing you will need is the domain name. You’ll need one per node, so it makes sense to set up a subdomain for each one and set up redirects. Let’s say you own `awesome-validator.com` and run two nodes with the following IP addresses, names, and redirects: | Node name | Node IP | Redirect | | --------- | ---------- | ---------------------------- | | node-0 | 123.1.23.5 | node-0.awesome-validator.com | | node-1 | 5.46.34.25 | node-1.awesome-validator.com | 2. Verify redirects You can verify that redirects work by sending a request to the watchdog API on 3009 port using your domain name. 3. Issue SSL certificates Once you have all redirects for your nodes, you can move to the SSL certificates. You will need SSL certs issued by one of the [Trusted CAs](#trusted-authorities). Once you've decided on the certificate issuer, you have several options - issue a separate certificate for each subdomain (node-0.awesome-validator.com, node-1.awesome-validator.com) or issue a single Wildcard SSL for all nodes (\*.awesome-validator.com). As a result, you should have 2 main files saved and copied to the respective nodes: * Certificate file (usually called something like fullchain.pem or cert.pem) * Private key file (usually called something like privkey.pem, pk.pem) 4. Upload certificates to the SKALE Node Once you copied the certificate and private key file, all you have to do is to run the following command: ```shell theme={null} skale ssl upload -c $PATH_TO_CERT_FILE -k $PATH_TO_KEY_FILE ``` Add -f flag to the command to override existing certificates on the node Once certificates are uploaded, you can check them by running: ```shell theme={null} skale ssl status ``` 5. Set domain name for your node in the SKALE Manager smart contracts To set the domain name of the registered node with SKALE manager, execute the following node-cli command on each respective node: ```shell theme={null} skale node set-domain -d node-0.awesome-validator.com ``` See full reference for the `set-domain` command [https://github.com/skalenetwork/node-cli#domain-name\[here](https://github.com/skalenetwork/node-cli#domain-name\[here)]. In the recent version of the SKALE Manager smart contracts domain name is required during the node registration Be sure get a domain in advance to provide it when adding a new node to the network: ```shell theme={null} skale node register --ip 1.2.3.4 -d node-0.awesome-validator.com --name node-0-AV ``` If you already registered your node, then command will fail. If you are trying to set the domain for an already registered node, simply use `skale node set-domain -d node-0.awesome-validator.com`. See full reference for the `register` command [https://github.com/skalenetwork/node-cli#node-registration\[here](https://github.com/skalenetwork/node-cli#node-registration\[here)]. ## Trusted authorities When choosing CA for getting your SSL certificate, consider a source that will be trusted by most major browsers, operating systems, and other devices. Here’s a list of links that can help you with your decision: * List of major CA providers: [https://en.wikipedia.org/wiki/Certificate\_authority#Providers](https://en.wikipedia.org/wiki/Certificate_authority#Providers) * CAs trusted by Mozilla: [https://wiki.mozilla.org/CA/Included\_Certificates](https://wiki.mozilla.org/CA/Included_Certificates) * CAs trusted by Apple: [https://support.apple.com/en-us/HT204132](https://support.apple.com/en-us/HT204132), [https://support.apple.com/en-us/HT209144](https://support.apple.com/en-us/HT209144) Also, here’s a list of the most popular and well-known SSL providers: * DigiCert [https://www.digicert.com/](https://www.digicert.com/) * GeoTrust [https://www.geotrust.com/](https://www.geotrust.com/) * RapidSSL [https://www.rapidssl.com/](https://www.rapidssl.com/) Besides that, usually, you could purchase an SSL certificate from your domain provider. That could be the most convenient way for you to manage everything from a single place: * GoDaddy [https://godaddy.com/web-security/ssl-certificate](https://godaddy.com/web-security/ssl-certificate) * NameCheap [http://namecheap.com/](http://namecheap.com/) * Name.com [https://www.name.com/ssl](https://www.name.com/ssl) Most of them usually providing you with a few options like RapidSSL and DigiCert. Certificates provided by Letsencrypt are widely used now and trusted by most operating systems and devices, so it's OK to use them for your node, but don't forget about renewals since Letsencrypt certs expire in 3 months. You can read more about Letsencrypt security and trust here: [https://letsencrypt.org/certificates/](https://letsencrypt.org/certificates/) and [https://letsencrypt.org/2018/08/06/trusted-by-all-major-root-programs.html](https://letsencrypt.org/2018/08/06/trusted-by-all-major-root-programs.html) Generally, Letsencrypt SSL certs are considered safe, but there are some opponents to the idea of a 'free SSL certs for everybody': [https://medium.com/swlh/why-lets-encrypt-is-a-really-really-really-bad-idea-d69308887801](https://medium.com/swlh/why-lets-encrypt-is-a-really-really-really-bad-idea-d69308887801) ## Reference Letsencrypt tutorial This brief tutorial shows you how to generate a wildcard SSL using Letsencrypt. 1. Install Certbot [https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx](https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx) 2. Run ```shell theme={null} certbot certonly --standalone -d my.domain.com ``` 3. Copy .pem files to secure place ```shell theme={null} cp *.pem ~/[SECURE_DIR] ``` # Node CLI Source: https://docs.skale.space/validators/node-cli/overview The Node CLI offers a command line option for setting up, registering, and maintaining SKALE nodes. The **SKALE Network Node CLI** is a command-line interface designed to interact with and manage supernodes within the SKALE Network. The Node CLI tool contains functionality to help support with managing SKALE SKALE Chain containers, setting up and managing monitoring, supernode resource allocation, and more. Access documentation for v2.0, the latest release [here](/validators/node-cli/releases/v2-0) # Node CLI - v2.0 Source: https://docs.skale.space/validators/node-cli/releases/v2-0 The Node CLI offers a command line option for setting up, registering, and maintaining SKALE nodes. ## Installation Ensure that the following packages are installed: * **docker** * **docker-compose** (1.27.4+) 1. Download the executable ```shell theme={null} VERSION_NUM={put the version number here} && sudo -E bash -c "curl -L https://github.com/skalenetwork/node-cli/releases/download/$VERSION_NUM/skale-$VERSION_NUM-`uname -s`-`uname -m` > /usr/local/bin/skale" ``` 2. Apply executable permissions to the downloaded binary ```shell theme={null} chmod +x /usr/local/bin/skale ``` 3. Test the Installation ```shell theme={null} skale --help ``` ## Top Level Commands ### Info Prints build info. ```shell theme={null} skale info ``` ### Version Prints version number. ```shell theme={null} skale version ``` **Optional Arguments** * `--short` prints the version only without additional text. ## Node Commands ### Node Information Retrieves the base info about SKALE node. ```shell theme={null} skale node info ``` **Optional Arguments** * `-f/--format` accepts either **json** or **text** as valid input which formats the output accordingly ### Node Initialization Initialize a SKALE node on current machine. **Please avoid re-initialization**: First run `skale node info` to confirm current state of initialization. ```shell theme={null} skale node init [ENV_FILEPATH] ``` **Required Parameters** 1. `ENV_FILEPATH` is a path to the .env file (required parameters are listed in the `skale init` command) SGX\_SERVER\_URL= DISK\_MOUNTPOINT= DOCKER\_LVMPY\_STREAM= CONTAINER\_CONFIGS\_STREAM= ENDPOINT= IMA\_ENDPOINT= MANAGER\_CONTRACTS\_ABI\_URL= IMA\_CONTRACTS\_ABI\_URL= FILEBEAT\_URL= TG\_API\_KEY= TG\_CHAT\_ID= MONITORING\_CONTAINERS= ### Node Initialization from Backup Restores a SKALE node on another machine. ```shell theme={null} skale node restore [BACKUP_PATH] [ENV_FILEPATH] ``` **Required Parameters** 1. `BACKUP_PATH` is the path to the archive with backup data generated by `skale node backup` command 2. `ENV_FILEPATH` is the path to .env file (required parameters are listed in the `skale init` command) ### Node Backup Generates a backup file to restore SKALE node on another machine. ```shell theme={null} skale node backup [BACKUP_FOLDER_PATH] ``` **Required Parameters** 1. `BACKUP_FOLDER_PATH` is the path to the folder where the backup tarball will be saved ### Node Signature Generates a node signature that is used to link node to a specific validator. ```shell theme={null} skale node signature [VALIDATOR_ID] ``` **Required Parameters** 1. `VALIDATOR_ID` - id of the validator ### Node Registration ```shell theme={null} skale node register ``` **Required Arguments** * `--ip` is the public IP for RPC connections and consensus * `--domain`/`-d` is the SKALE node domain name * `--name` is the SKALE node name **Optional Arguments** * `--port` is a public port, the beginning of the port range for node SKALE Chains (default: *10000*) ### Node Update Updates a SKALE node on the current machine. ```shell theme={null} skale node update [ENV_FILEPATH] ``` **Required Arguments** * `ENV_FILEPATH` is the path to env file where parameters are defined **Optional Arguments** * `--yes` executes without additional confirmation You can also specify a file with environment variables which will update parameters in env file used during skale node init. ### Node Turn-off Turns off the SKALE node on current machine and optionally sets it to maintenance mode. ```shell theme={null} skale node turn-off ``` **Optional Arguments** * `--maintenance-on` sets the SKALE node into maintenance mode before turning off * `--yes` executes without additional confirmation ### Node Turn-on Turns on SKALE node on current machine and optionally disables maintenance mode. ```shell theme={null} skale node turn-on [ENV_FILEPATH] ``` **Optional Arguments** **Required Parameters** 1. `ENV_FILEPATH` is the path to env file where parameters are defined **Optional Arguments** * `--maintenance-off` turns off maintenance mode after turning on the node * `--yes` executes without additional confirmation You can also specify a file with environment variables which will update parameters in env file used during skale node init. ### Enable Maintenance Mode Enables maintenance mode on the node. ```shell theme={null} skale node maintenance-on ``` **Optional Arguments** * `--yes` executes without additional confirmation ### Disable Maintenance Mode ```shell theme={null} skale node maintenance-off ``` ### Domain name Set SKALE node domain name ```shell theme={null} skale node set-domain ``` **Optional Arguments** * `--domain`/`-d` the SKALE node domain name * `--yes` executes without additional confirmation ## Wallet commands Commands related to Ethereum wallet associated with SKALE node ### Wallet information ```shell theme={null} skale wallet info ``` **Optional Arguments** * `-f/--format` formats the output. Valid inputs are **json** and **text** ### Send ETH tokens Sends ETH tokens from the SKALE node wallet to a specific address. ```shell theme={null} skale wallet send [ADDRESS] [AMOUNT] ``` **Required Parameters** 1. `ADDRESS` is the Ethereum receiver address 2. `AMOUNT` is the Amount of ETH tokens to send **Optional Arguments** * `--yes` executes without additional confirmation ## SKALE Chain commands ### List SKALE Chains on Node Lists the SKALE Chains served by the connected node. ```shell theme={null} skale SKALE Chains ls ``` ### Get SKALE Chain Config ```shell theme={null} skale SKALE Chains config SCHAIN_NAME ``` ### Get SKALE Chain DKG Status Lists the DKG status for each SKALE Chain on the node. ```shell theme={null} skale SKALE Chains dkg ``` ### Get SKALE Chain Info Shows information about a specified SKALE Chain on the node. ```shell theme={null} skale SKALE Chains info SCHAIN_NAME ``` **Required Parameters** 1. `SCHAIN_NAME` is a valid SKALE Chain on the node **Optional Arguments** * `--json` shows info in JSON format ### Repair SKALE Chain Turn on repair mode for SKALE Chain ```shell theme={null} skale SKALE Chains repair SCHAIN_NAME ``` **Required Parameters** 1. `SCHAIN_NAME` is a valid SKALE Chain on the node **Optional Arguments** * `--yes` executes repair without additional confirmation ## Health commands ### List Containers Lists all SKALE containers running on the connected node. ```shell theme={null} skale health containers ``` **Optional Arguments** * `-a/--all` lists all containers (by default - only running) ### Healthcheck for SKALE Chains Shows health check results for all SKALE Chains on the node. ```shell theme={null} skale health SKALE Chains ``` **Optional Arguments** * `--json` shows info in JSON format ### SGX Commands Checks status of the SGX server. Returns the SGX server URL and connection status. ```shell theme={null} $ skale health sgx ``` #### Example Output ```shell theme={null} SGX server status: ┌────────────────┬────────────────────────────┐ │ SGX server URL │ https://0.0.0.0:1026/ │ ├────────────────┼────────────────────────────┤ │ Status │ CONNECTED │ └────────────────┴────────────────────────────┘ ``` ## SSL Commands ### SSL Status Retrieves the status of the SSL certificates on the node. ```shell theme={null} skale ssl status ``` ### Upload Certificates Uploads new SSL certificates. ```shell theme={null} skale ssl upload ``` **Required Arguments** * `-c/--cert-path` is the path to the certificate file * `-k/--key-path` is the path to the key file **Optional Arguments** * `-f/--force` overwrites the existing certificates ## Logs Commands ### CLI Logs Fetch Node CLI logs. ```shell theme={null} skale logs cli ``` **Optional Arguments** * `--debug` shows debug logs with a more verbose output ### Dump Logs Dumps all logs from the connected node. ```shell theme={null} skale logs dump [PATH] ``` **Required Parameters** 1. `PATH` is the required path to dump the logs to **Optional Arguments** * `--container`, `-c` - Dump logs only from specified container ## Resources Allocation Commands ### Show Allocation File Show the resources allocation file. ```shell theme={null} skale resources-allocation show ``` ### Generate/Update Generate (or updates if already exists) the allocation file. ```shell theme={null} skale resources-allocation generate [ENV_FILEPATH] ``` **Required Parameters** 1. `ENV_FILEPATH` is the path to .env file (required parameters are listed in the `skale init` command) **Optional Arguments** * `--yes` generates without additional confirmation * `-f/--force` rewrites allocation file if it exists ## Validate commands ### Validate ABI Checks whether ABI files contain valid JSON data. ```shell theme={null} skale validate abi ``` **Optional Argument** * `--json` shows the validation result in json format ## Exit codes Exit codes conventions for SKALE CLI tools. | Code | Description | | ---- | ----------------------- | | 0 | Everything is OK | | 1 | General error exit code | | 3 | Bad API response | | 4 | Script execution error | | 7 | Bad user error | | 8 | Node state error | # Upgrade to v3.0.3 Source: https://docs.skale.space/validators/releases/3-0-3-upgrade SKALE node upgrade steps from 3.0.2 to 3.0.3 ## Upgrade steps from 3.0.2 to 3.0.3 1. Update the following parameters to the new values ```bash theme={null} CONTAINER_CONFIGS_STREAM=3.0.3 ``` 2. Execute update ```bash theme={null} skale node update .env --yes ``` # Upgrade to v3.1.0 Source: https://docs.skale.space/validators/releases/3-1-0-upgrade SKALE node upgrade steps from 3.0.3 to 3.1.0 ## Upgrade steps from 3.0.3 to 3.1.0 1. Download new node-cli binary ```bash theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.5.0/skale-2.5.0-Linux-x86_64 > /usr/local/bin/skale ``` 2. Verify checksum using sha512sum command ```bash theme={null} sha512sum /usr/local/bin/skale ``` Expected checksum ```bash theme={null} 2a409323eae26b14e4c5c018ae0b164da81a67793bcc1fcb96a266afab4e8672c587f1a6c4fd2e4c1d8ee1644616efc2e28249549108c0711c9cdce59b9047a8 ``` 3. Make node-cli executable ```bash theme={null} chmod +x /usr/local/bin/skale ``` 4. Update the following parameters to the new values ```bash theme={null} CONTAINER_CONFIGS_STREAM=3.1.0 ``` 5. Execute update From 3.1.0 it will not be possible to run upgrade while critical network operation is in progress. But before changes are made --unsafe option is required to run upgrade. ```bash theme={null} skale node update .env --yes --unsafe ``` # Upgrade to v3.1.1 Source: https://docs.skale.space/validators/releases/3-1-1-upgrade SKALE node upgrade steps from 3.1.0 to 3.1.1 The firewall mechanism was updated in version 3.1.1. As a result, please proceed with the following steps with extra caution. Ensure that you perform these actions on each node individually. ## Upgrade steps for the SGX server 1. Backup `sgx_data` folder and SGX backup key 2. Create new SGX server on Ubuntu 22.04 3. Clone sgxwallet repo ```bash theme={null} git clone https://github.com/skalenetwork/sgxwallet.git && git checkout stable ``` 4. Install required packages ```bash theme={null} apt-get install docker.io docker-compose build-essential ``` 5. Enable sgx ```bash theme={null} cd sgxwallet && sudo ./sgx_enable ``` 6. Install sgx driver ```bash theme={null} cd scripts; sudo ./sgx_linux_x64_driver_2.11.b6f5b4a.bin; cd .. ``` 7. Configure firewall between nodes and SGX server 8. Upload `sgx_data` folder to `sgxwallet/run_sgx/sgx_data` on the new server 9. Add `-b` option in `command` section of `run_sgx/docker-compose.yml` 10. Set SGX wallet version to `1.9.0-stable.2` in `run_sgx/docker-compose.yml` 11. Run sgxwallet ```bash theme={null} cd run_sgx && docker-compose up -d ``` ## Upgrade steps for the node server 1. Turn off the node ```bash theme={null} skale node turn-off --yes --unsafe ``` 2. Backup the node ```bash theme={null} skale node backup . ``` Save the data on another machine 3. Upgrade packages ```bash theme={null} sudo apt update && sudo apt upgrade sudo reboot ``` 4. Upgrade to Ubuntu 22.04 ```bash theme={null} do-release-upgrade reboot ``` 5. Turn off docker-lvmpy ```bash theme={null} systemctl stop docker-lvmpy && systemctl disable docker-lvmpy ``` 6. Make sure nftables is installed ```bash theme={null} sudo apt install nftables docker-compose-plugin ``` 7. Disable ufw ipv6 configuration ```bash theme={null} sed -i 's/IPV6=yes/IPV6=no/' /etc/default/ufw. ``` 8. Reload ufw ```bash theme={null} ufw reload ``` 9. Download new node-cli binary ```bash theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.6.0/skale-2.6.0-Linux-x86_64 > /usr/local/bin/skale ``` 10. Verify node-cli binary hash sum ```bash theme={null} sha512sum /usr/local/bin/skale ``` Expected checksum ```bash theme={null} 15b2aade24223da4f84ec79bd820d57f852fd7a5d78f10652823629da28aab5db49a5815a2be0c894bb00b99324b00b7d9da2ab1518ddc11f304378af54b427c ``` 11. Make node-cli executable ```bash theme={null} chmod +x /usr/local/bin/skale ``` 12. Update the following parameters to the new values ```bash theme={null} CONTAINER_CONFIGS_STREAM=3.1.1 SGX_WALLET_URL=https://[NEW SGX WALLET SERVER IP]:1026 ``` 13. Execute update ```bash theme={null} skale node update .env --yes ``` 14. Restart nftables and docker services Proceed with the execution in close collaboration with the core team, ensuring that the chains are fully stable beforehand. ```bash theme={null} systemctl restart nftables && systemctl restart docker ``` # Upgrade to v3.1.1 (Local SGX) Source: https://docs.skale.space/validators/releases/3-1-1-upgrade-local-sgx SKALE node upgrade steps from 3.1.0 to 3.1.1 The firewall mechanism was updated in version 3.1.1. As a result, please proceed with the following steps with extra caution. Ensure that you perform these actions on each node individually. ## Upgrade steps for the node server 1. Turn off sgxwallet ```bash theme={null} cd sgxwallet/run_sgx/ && docker-compose down && cd ../../ ``` 2. Turn off the node ```bash theme={null} skale node turn-off --unsafe --yes ``` 3. Backup the node ```bash theme={null} skale node backup . ``` Save the data on another machine 4. Backup `sgx_data` folder and SGX backup key. Save the data on another machine 5. Upgrade packages ```bash theme={null} sudo apt update && sudo apt upgrade sudo reboot ``` 6. Upgrade to Ubuntu 22.04 ```bash theme={null} do-release-upgrade reboot ``` 7. Turn off docker-lvmpy ```bash theme={null} systemctl stop docker-lvmpy && systemctl disable docker-lvmpy ``` 8. Make sure nftables is installed ```bash theme={null} sudo apt update && sudo apt install nftables docker-compose-plugin ``` 9. Disable ufw ipv6 configuration ```bash theme={null} sed -i 's/IPV6=yes/IPV6=no/' /etc/default/ufw. ``` 10. Reload ufw ```bash theme={null} ufw reload ``` 11. Pull latest changes from sgxwallet ```bash theme={null} cd sgxwallet/run_sgx && git checkout stable && git pull ``` 12. Add `-b` option in the `command` section of `run_sgx/docker-compose.yml` 13. Run sgxwallet ```bash theme={null} cd run_sgx && docker-compose up -d ``` 14. Download new node-cli binary ```bash theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.6.0/skale-2.6.0-Linux-x86_64 > /usr/local/bin/skale ``` 15. Verify node-cli binary hash sum ```bash theme={null} sha512sum /usr/local/bin/skale ``` Expected checksum ```bash theme={null} 15b2aade24223da4f84ec79bd820d57f852fd7a5d78f10652823629da28aab5db49a5815a2be0c894bb00b99324b00b7d9da2ab1518ddc11f304378af54b427c ``` 16. Make node-cli executable ```bash theme={null} chmod +x /usr/local/bin/skale ``` 17. Update the following parameters to the new values ```bash theme={null} CONTAINER_CONFIGS_STREAM=3.1.1 ``` 18. Execute update ```bash theme={null} skale node update .env --yes ``` 19. Restart nftables and docker services Proceed with the execution in close collaboration with the core team, ensuring that the chains are fully stable beforehand. ```bash theme={null} systemctl restart nftables && systemctl restart docker nft add rule inet firewall skale tcp dport 1026-1031 drop ``` # Upgrade to v3.1.2 Source: https://docs.skale.space/validators/releases/3-1-2-upgrade SKALE node upgrade steps from 3.1.1 to 3.1.2 ## Upgrade steps for the SGX server 1. Go to run\_sgx folder ```bash theme={null} cd sgxwallet/run_sgx ``` 2. Backup `sgx_data` folder and SGX backup key 3. Pull recent changes ```bash theme={null} git checkout stable && git pull ``` If there any conflicts, please, contact the SKALE team 4. Restart the container ```bash theme={null} docker compose down && docker compose up -d ``` ## Upgrade steps for the node server 1. Disable docker-lvmpy ```bash theme={null} systemctl stop docker-lvmpy && systemctl disable docker-lvmpy ``` 2. Download new node-cli binary ```bash theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.6.1/skale-2.6.1-Linux-x86_64 > /usr/local/bin/skale ``` 3. Verify node-cli binary hash sum ```bash theme={null} sha512sum /usr/local/bin/skale ``` Expected checksum ```bash theme={null} 5faf3eb1bffe00a4172f7f34570b84bed2ff051b40b3bc97b249ee74e07ea2dee5991297bbe8071c50ad01fa718d9ebbe9fc4d397324b4fa05950d11e891a179 ``` 4. Make node-cli executable ```bash theme={null} chmod +x /usr/local/bin/skale ``` 5. Run update procedure ```bash theme={null} skale node update .env --yes ``` # Upgrade to v4.0.0 Source: https://docs.skale.space/validators/releases/4-0-0-upgrade SKALE node upgrade steps from 3.1.2 to 4.0.0 ## Upgrade steps for the node server 1. Disable docker-lvmpy ```bash theme={null} systemctl stop docker-lvmpy && systemctl disable docker-lvmpy ``` 2. Download new node-cli binary ```bash theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.6.2/skale-2.6.2-Linux-x86_64 > /usr/local/bin/skale ``` 3. Verify node-cli binary hash sum ```bash theme={null} sha512sum /usr/local/bin/skale ``` Expected checksum ```bash theme={null} e012e18b062015c234e364bc0cd49ee40e65243d725f2261a4a5a2d516ac2fd86d59f3b9104f15f4fbe4045ec3ead018cc7ec159d079001fe9293617602741e4 ``` 4. Make node-cli executable ```bash theme={null} chmod +x /usr/local/bin/skale ``` 5. Update the following parameters to the new values ```bash theme={null} CONTAINER_CONFIGS_STREAM=4.0.0 ``` 6. Run update procedure ```bash theme={null} skale node update .env --yes ``` # Upgrade to v4.0.1 Source: https://docs.skale.space/validators/releases/4-0-1-upgrade SKALE node upgrade steps from 4.0.0 to 4.0.1 ## Upgrade steps for the node server 1. Update the following parameters to the new values ```bash theme={null} CONTAINER_CONFIGS_STREAM=4.0.1 ``` 2. Run update procedure ```bash theme={null} skale node update .env --yes ``` # Run Archive Node Source: https://docs.skale.space/validators/run-archive-node Run a SKALE Network Archive Node Within the realm of nodes, there are distinct types tailored for specific purposes. For instance, archive nodes specialize in storing comprehensive historical data of the blockchain and providing it upon request. This sets them apart from full nodes, which focus on recent blockchain states, and light nodes, primarily reliant on data from full nodes. Archive nodes build archival blockchain data quickly and efficiently, and they’re useful for querying arbitrary historical data, like a user’s balances on a specific block. ## General Steps 1. Provision a machine. 2. Notify core team of IP/IPs range (`x.x.x.x:x.x.x.x`) and SKALE Chain for archive. 3. Complete archive node setup. ## Recommended Machine Requirements * A Linux x86\_64 machine * Ubuntu 20.04 * 4 physical cores * 16GB RAM * 100GB root storage * 2TB attached storage ## Node Network Requirements * Ports 80, 443, 3009, and 10000–18192, and ICMP IPv4 should not be closed by any firewall software. Firewall setup is a part of skale binary. Any custom configuration may break or reduce the security of the system. ## Node Software Prerequisites * 8GB Swap * docker * docker-compose → 1.29.2 * iptables-persistent, btrfs-progs, lsof, lvm2, psmisc, and apt packages ## From Fresh Machine ```shell theme={null} sudo apt-get update # Install Docker and Docker-compose sudo apt-get install docker.io sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # Install other dependencies sudo apt install iptables-persistent btrfs-progs lsof lvm2 psmisc # Setup Swapfile sudo fallocate -l 8G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile sudo cp /etc/fstab /etc/fstab.bak echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab ``` ## SKALE Archive Node Implementation Before proceeding, notify the core team of the IP address of your archive node. It's suggested to use an elastic IP for the whitelisted IP address. 1. ##### Download sync-node-cli tool ```shell theme={null} VERSION_NUM=2.3.0-sync-node.1 && sudo -E bash -c "curl -L https://github.com/skalenetwork/node-cli/releases/download/$VERSION_NUM/skale-$VERSION_NUM-`uname -s`-`uname -m`-sync > /usr/local/bin/skale" ``` 2. ##### Verify checksum ```shell theme={null} # sha512sum /usr/local/bin/skale b35d88fba1269f859a996a1fefd9520e30f965c092d3ba716f8d7dd88694b821dc225641f75a60cef2364d0fbeb986d314f592fe4b4238c716acf0834d2d6146 /home/ubuntu/dist/skale-2.3.0-sync-node.1-Linux-x86_64-sync ``` 3. ##### Apply executable permissions to the downloaded binary: ```shell theme={null} chmod +x /usr/local/bin/skale ``` 4. ##### Test the installation ```shell theme={null} sudo skale --help ``` 5. ##### Prepare `.env` file: ```shell theme={null} CONTAINER_CONFIGS_STREAM="2.2.1-sync" ENDPOINT=[YOUR GETH/INFURA ENDPOINT] IMA_CONTRACTS_ABI_URL=https://raw.githubusercontent.com/skalenetwork/concepts/master/releases/mainnet/IMA/1.5.0/mainnet/abi.json MANAGER_CONTRACTS_ABI_URL=https://raw.githubusercontent.com/skalenetwork/concepts/master/releases/mainnet/skale-manager/1.9.3/skale-manager-1.9.3-mainnet-abi.json DISK_MOUNTPOINT=[The second disk /dev/sdb or /dev/xvdb (this is an example. You just need to use your 2TB block device)] DOCKER_LVMPY_STREAM="1.0.2-stable.0" ENFORCE_BTRFS=True/False ENV_TYPE="mainnet" SCHAIN_NAME=[SKALE Chain NAME FOR YOUR ARCHIVE NODE] ``` ENFORCE\_BTRFS=True formats your attached device as btrfs even if there is another filesystem. Backup the storage before running the command. 6. ##### Init archive node: ```shell theme={null} sudo skale sync-node init --archive --catchup --historic-state [ENV_FILE_PATH] ``` . Wait until your archive node will be inited. . After the node is successfully inited, wait until chain container will download the snapshot and starts catchup blocks (usually 15–30 minutes). ## Setup SSL certs Node SSL certificates support secure communication with chain endpoints. By default, each node of a SKALE Chain listens on HTTP and WS ports. If SSL certs exist on the node, then HTTPS and WSS ports are also turned on. To upload SSL certs to the node run: ```shell theme={null} sudo skale ssl upload -c $PATH_TO_CERT_FILE -k $PATH_TO_KEY_FILE ``` To check the status of your certs run: ```shell theme={null} sudo skale ssl status ``` Once certs are uploaded, HTTPS/WSS endpoints should become available for your chain. ## Update archive node Update archive SKALE node on current machine ```shell theme={null} sudo skale sync-node update [ENV_FILEPATH] ``` Options: * `--yes` - update without additional confirmation Arguments: * `ENV_FILEPATH` - path to env file where parameters are defined You can just update a file with environment variables used during `sudo skale sync-node init`. # Run a Supernode Source: https://docs.skale.space/validators/run-supernode Setup Process for new SKALE Validator ## Setup Validator 1. **Prepare for Validator Registration** Before registering as a validator, complete the following preparations: **Obtain Ethereum Software Wallet or Hardware wallet** Get a Ledger, Hardware or Software wallet ready like Metamask/Portis/Bitski/Torus/myetherwallet. This wallet will be used for receiving delegation, bounties, and self delegating. **Fund Validator Wallet with ETH** Be ready to have some ETH to accept delegations and link your validator supernode. Remember these are transactions with SKALE Manager on Ethereum, and therefore require ETH. *Minimum ETH: 1.0* **Get SKALE Manager contract ABIs from SKALE:** [Github](https://github.com/skalenetwork/skale-network/blob/master/releases/mainnet/skale-manager/1.12.0/skale-manager-1.12.0-mainnet-abi.json) **Fund SRW Wallet with ETH** Be ready to fund ETH in your self-recharging wallet (SRW). See [Self-recharging wallet documentation](/validators/validator-cli/self-recharging-wallets). **Decide your commission fee** Decide on your commission fee. This cannot be changed once it is set. To set a new commission fee, you'll have to register a new validator. This new validator would have to link supernodes and accept delegations for it (old validator supernodes and delegations cannot be rolled into a new validator commission fee). *Reference Documents* * [Validator & Token Economics](https://skale.network/blog/validator-economics) * [Tokenomics](https://skale.network/tokenomics) 2. **Install SKALE Validator CLI** SKALE Validator CLI is the validator client interface for registering a new validator into network or handling additional delegation services where validators can self delegate or token holders can delegate to a validator. These are the type of operations that can be done with the Validator CLI: * Register Validator (Set Commission Rate or Minimum delegation amount) * Accept pending delegations * Link all validator supernode addresses to a validator wallet address * Request or cancel a delegation [See the SKALE Validator CLI documentation](/validators/validator-cli/overview) Validator CLI doesn't have to be installed in the same server as the node-cli. This can be set up on Mac or Linux. This does not need to be included in every supernode. Setting up once per validator organization is sufficient. Download the SKALE Validator CLI binary: ```shell theme={null} VERSION_NUM=1.3.3 && sudo -E bash -c "curl -L https://github.com/skalenetwork/validator-cli/releases/download/$VERSION_NUM/sk-val-$VERSION_NUM-`uname -s`-`uname -m` > /usr/local/bin/sk-val" ``` Apply executable permissions to the binary: ```shell theme={null} chmod +x /usr/local/bin/sk-val ``` Set SKALE Manager contracts info and set the endpoint: ```shell theme={null} sk-val init -e [ENDPOINT] -c [ABI] --wallet [software/ledger] ``` Required arguments: * `--endpoint/-e` - RPC endpoint of the supernode in the network where SKALE manager is deployed (http or https). Example is [https://my.geth.node.ip/](https://my.geth.node.ip/).. * `--contracts-url/-c` - URL to SKALE Manager contracts ABI and addresses * `-w/--wallet` - Type of the wallet that will be used for signing transactions (software or ledger) 3. **Setup Wallet** For software wallet, save private key into a file (replace `[YOUR PRIVATE KEY]` with your wallet private key): ```shell theme={null} echo [YOUR PRIVATE KEY] > ./pk.txt ``` For Ledger wallet, install ETH ledger application and initialize device with `setup-ledger` command: ```shell theme={null} sk-val wallet setup-ledger --address-index [ADDRESS_INDEX] --keys-type [KEYS_TYPE] ``` Required arguments: * `--address-index` - Index of the address to use (starting from `0`) * `--keys-type` - Type of the Ledger keys (live or legacy) Make sure you enabled blind signing on ETH application settings. Otherwise transactions won't work 4. **Register as a new SKALE validator** DON'T REGISTER A NEW VALIDATOR IF YOU ALREADY HAVE ONE! check : `sk-val validator ls`. For additional supernode set up, please go to Step 3. ```shell theme={null} sk-val validator register -n [NAME] -d [DESCRIPTION] -c [COMMISSION_RATE] --min-delegation [MIN_DELEGATION] ``` Required arguments: * `--name/-n` - Validator name * `--description/-d` - Validator description (preferably organization info) * `--commission-rate/-c` - Commission rate (percent %) * `--min-delegation` - Validator minimum delegation amount Optional arguments: * `--pk-file` - Path to file with private key (only for `software` wallet type) * `--gas-price` - Gas price value in Gwei for transaction (if not specified doubled average network value will be used) * `--yes` - Confirmation flag 5. **Ensure validator is added to the trusted list** To ensure that your validator is added to trusted contact SKALE team. This is a temporary process, which will be removed as the community matures. ## Setup SGX 6. **Understand SGX Overview** Due date: 23rd of each month SGX is a secure storage for BLS private key shares, which are used in consensus to sign new blocks. SGX is also used for private key shares. SKALE DKG uses Intel® SGX server to store account and BLS keys and all the data related to DKG process and it also uses the random number generator provided by Intel® SGX. For more information, please check [here](/concepts/dkg-with-bls). Clients connect to the server, authenticate to it using TLS 1.0 protocol with client certificates, and then issue requests to the server to generate crypto keys and perform cryptographic operations. The keys are generated inside the secure SGX enclave and never leave the enclave unencrypted. 7. **Prepare SGX Server** To be able to set up an SGXWallet, validators are required to have SGX compatible servers. Before installing SGXWallet, validators must make sure that SGX is enabled in the server. **Server Requirements:** * Ubuntu 22.04 LTS (Jammy Jellyfish) * SGX-enabled Intel processor * Minimum 8 GB * Swap size equals to half (1/2) of RAM size 8. **Configure Network for SGX** It's required to setup VPN between supernodes and SGX server. Ports 1026-1031 should open only to SKALE supernodes, not public ports should be accessible by supernode. SGXWallet can support up to 5 SKALE supernodes. If you have more, you should setup additional server. 9. **Install and Configure Packages for SGX** Before running SGXWallet install the following packages **Install general tools:** ```shell theme={null} sudo apt-get install -y build-essential make cmake gcc g++ yasm python libprotobuf10 flex bison automake libtool texinfo libgcrypt20-dev libgnutls28-dev ``` **Install Docker:** ```shell theme={null} sudo apt-get install -y docker ``` **Install docker.io:** ```shell theme={null} sudo apt-get install -y docker.io ``` **Install docker compose:** ```shell theme={null} sudo apt-get install -y docker-compose-plugin ``` **Install cpuid and libelf-dev packages:** ```shell theme={null} sudo apt-get install -y libelf-dev cpuid ``` **Verify your processor supports Intel SGX with:** ```shell theme={null} cpuid | grep SGX: ``` After installing docker make sure that `live-restore` option is enabled in `/etc/docker/daemon.json`. See more info in the [docker docs](https://docs.docker.com/config/containers/live-restore/). **Disable automatic updates** It's recommended to only update the SGXWallet server if there are critical security fixes. This is because SGXWallet is based on new low level technology, and kernel updates may break the system. Currently SGX is tested on 5.15\* kernels. It's best to avoid minor version updates too. To make sure `apt update` won't update the kernel you should use apt-mark hold command: ```shell theme={null} sudo apt-mark hold linux-generic linux-image-generic linux-headers-generic ``` Also if you configured unattended upgrades, you should make sure kernel won't update automatically. To do this, add the following lines to `/etc/apt/apt.conf.d/50unattended-upgrades` file: ```shell theme={null} Unattended-Upgrade::Package-Blacklist { "linux-generic"; "linux-image-generic"; "linux-headers-generic"; }; ``` **Output** ```shell theme={null} SGX: Software Guard Extensions supported = true ``` 10. **Download SGXWallet source code** Clone SGX Wallet Repository to your SGX compatible Server: ```shell theme={null} git clone https://github.com/skalenetwork/sgxwallet/ cd sgxwallet git checkout tags/1.9.1-stable.1 ``` 11. **Enable SGX** SGX Wallet repository includes the sgx\_enable utility. To enable SGX run: ```shell theme={null} sudo ./sgx_enable ``` **Install SGX Library:** ```shell theme={null} cd scripts sudo ./sgx_linux_x64_driver_2.5.0_2605efa.bin cd .. ``` **System Reboot:** Reboot your machine after driver install! **Check driver installation:** To check that isgx device is properly installed run this command: ```shell theme={null} ls /dev/isgx ``` For newer machine device should be ```shell theme={null} ls /dev/sgx_enclave ``` If you don't see the neither of isgx and sgx\_enclave device, you need to troubleshoot your driver installation from [here](https://github.com/skalenetwork/sgxwallet/blob/develop/docs/common/enabling-sgx.md). **Another way to verify Intel SGX is enabled in BIOS:** ***If you already executed the previous steps please move to STEP 3*** Enter BIOS by pressing the BIOS key during boot. The BIOS key varies by manufacturer and could be F10, F2, F12, F1, DEL, or ESC. Usually Intel SGX is disabled by default. To enable: find the Intel SGX feature in BIOS Menu (it's usually under the "Advanced" or "Security" menu) Set SGX in BIOS as enabled (preferably) or software-controlled. save your BIOS settings and exit BIOS. Enable "software-controlled" SGX Software-controlled means that SGX needs to be enabled by running a utility. 12. **Update docker-compose.yaml** Open run\_sgx directory ```shell theme={null} cd sgxwallet/run_sgx; ``` On some machines, the SGX device isn't **/dev/mei0** but a different device, such as **/dev/bs0** or **/dev/sg0**. In this case please edit docker-compose.yml on your machine to specify the correct device to use: ```shell theme={null} vi docker-compose.yml ``` make sure `image` is skalenetwork/sgxwallet:\<1.9.1-stable.1> in docker-compose and it will look like: ```shell theme={null} version: '3' services: sgxwallet: image: skalenetwork/sgxwallet:1.9.1-stable.1 network_mode: host devices: - "/dev/isgx" # leave on of them - "/dev/sgx_enclave" volumes: - ./sgx_data:/usr/src/sdk/sgx_data - /dev/urandom:/dev/random logging: driver: json-file options: max-size: "10m" max-file: "4" restart: unless-stopped command: -y -V healthcheck: test: ["CMD", "ls", "/dev/isgx", "/dev/sgx_enclave"] ``` 13. **Spin up SGXWallet Container** **Start SGX Wallet Containers** To run the server as a daemon: ```shell theme={null} sudo docker compose up -d ``` 14. **Securely save generated backup key** The backup key is automatically stored in *sgx\_data* directory. The filename of the key is sgx\_wallet\_backup\_key.txt, and is generated the first time the SGX wallet is started. **This key must be securely recorded and stored.** Be sure to store this key in a safe place, then go into a docker container and securely remove it with the following command: ```shell theme={null} docker exec -it bash && apt-get install secure-delete && srm -vz backup_key.txt ``` You should enable SSL/TLS for your SGX supernode. Make sure you finalize this before you move on to your next step. 15. **Backup sgx data** It's strongly recommended to backup sgx data regularly. The guide can be found [here](https://github.com/skalenetwork/sgxwallet/blob/stable/docs/backup-procedure.md). ## Supernode Setup 16. **Prepare Supernode Server** Due date: 23rd of each month After Setting up SGX Wallet and create certifications, validators can download the SKALE Node CLI executables register and maintain your SKALE supernode. This process downloads docker container images from docker hub and spins up SKALE supernode functionalities. Some base containers such as SKALE Admin, Bounty, TransactionManager will be created during installation for each supernode. This document contains instructions on how to setup supernode using SKALE Node CLI. Supernode server should follow compliance requirements which will be checked during installing SKALE supernode software. Please make sure: **General requirements** * A Linux x86\_64 machine * Ubuntu 22.04 LTS (Jammy Jellyfish) * Separate not mounted block device - 2 Tb * 8 physical cores * 32GB RAM * 16GB Swap More information can be found here: [Compliance requirements](/validators/compliance-requirements) 17. **Install Packages for Supernode** Before setting up supernode you should make sure that the following software is installed: * docker * docker-compose * nftables * lvm2 After docker installation make sure that the `live-restore` option is enabled in `/etc/docker/daemon.json`. See more info in the [docker docs](https://docs.docker.com/config/containers/live-restore/). You can install iptables-persistent using the following commands: ```shell theme={null} echo iptables-persistent iptables-persistent/autosave_v4 boolean true | sudo debconf-set-selections echo iptables-persistent iptables-persistent/autosave_v6 boolean true | sudo debconf-set-selections sudo apt install iptables-persistent -y ``` You should carefully control any automatic updates. In general avoid updates to the Linux kernel, docker, docker-compose, btrfs-progs. And take care when updating lvm2, iptables, iptables-persistent, and python. If you have any concerns or questions, please don't hesitate to reach out to SKALE Team leads on [Discord](http://discord.gg/skale). 18. **Install Node CLI** Download the Node CLI binary: ```shell theme={null} curl -L https://github.com/skalenetwork/node-cli/releases/download/2.6.2/skale-2.6.2-Linux-x86_64 > /usr/local/bin/skale ``` Verify the hashsum: ```shell theme={null} sha512sum /usr/local/bin/skale # Expected output: # e012e18b062015c234e364bc0cd49ee40e65243d725f2261a4a5a2d516ac2fd86d59f3b9104f15f4fbe4045ec3ead018cc7ec159d079001fe9293617602741e4 ``` Apply executable permissions and test the installation: ```shell theme={null} chmod +x /usr/local/bin/skale sudo skale --help ``` You should run node-cli commands using sudo More information can be found [here](/validators/node-cli/overview). 19. **Configure .env** Configuration parameters are passed to Node CLI through .env file. It should contain the following variables: * `CONTAINERS_CONFIG_STREAM` - git branch with containers versions config * `DISK_MOUNTPOINT` - Attached storage block device * `DOCKER_LVMPY_STREAM` - git branch of docker lvmpy volume driver for SKALE Chains * `ENDPOINT` - RPC endpoint of the supernode in the network where SKALE manager is deployed (`http` or `https`) * `FILEBEAT_HOST` - URL to the Filebeat log server * `IMA_CONTRACTS_ABI_URL` - URL to IMA contracts ABI and addresses * `IMA_ENDPOINT` - IMA endpoint to connect (should be the same as `ENDPOINT`). * `MANAGER_CONTRACTS_ABI_URL` - URL to SKALE Manager contracts ABI and addresses * `SGX_SERVER_URL` - URL to SGX server in the network (i.e. http(s)://host:port, do not add a trailing "/" after the port number ) * `ENV_TYPE` - network type (mainnet/testnet) `ENDPOINT`, `IMA_ENDPOINT`, `SGX_SERVER_URL`, `DISK_MOUNTPOINT` are server dependent. Other options depend on the network type. For the `{ENV_TYPE}` network .env will look like: ```txt theme={null} CONTAINER_CONFIGS_STREAM=4.0.1 DOCKER_LVMPY_STREAM=1.0.2-stable.0 FILEBEAT_HOST=filebeat.mainnet.skalenodes.com:5000 MANAGER_CONTRACTS_ABI_URL= https://raw.githubusercontent.com/skalenetwork/concepts/refs/heads/master/releases/mainnet/skale-manager/1.12.0/skale-manager-1.12.0-mainnet-abi.json IMA_CONTRACT_ABI_URL=https://raw.githubusercontent.com/skalenetwork/concepts/refs/heads/master/releases/mainnet/IMA/2.2.0/mainnet/abi.json ENV_TYPE=mainnet DISK_MOUNTPOINT=[DISK_MOUNTPOINT] IMA_ENDPOINT=[IMA_ENDPOINT] ENDPOINT=[ENDPOINT] SGX_SERVER_URL=[SGX_SERVER_URL] # Do not add a trailing "/" after the port number. ``` It's possible to configure Telegram based alert system by providing the following options: * `TG_API_KEY` - Telegram API key * `TG_CHAT_ID` - Telegram chat ID 20. **Enable SGX wallet autosign** Switch back to sgx server and go to `sgxwallet/run_sgx` folder ```shell theme={null} cd sgxwallet/run_sgx ``` Set `-s` option in docker-compose.yml ```shell theme={null} sed -i -E 's/(command: .*) *$/\1 -s/g' docker-compose.yml ``` Restart the container ```shell theme={null} docker compose down && docker compose up -d ``` 21. **Initialize Supernode** To install supernode on your server you should run `skale node init`. It will create necessary configuration files and run base services and containers. ```shell theme={null} sudo skale node init .env ``` **Example Output:** ```shell theme={null} 48914619bcd3: Pull complete db7a07cce60c: Pull complete d285532a5ada: Pull complete 8646278c4014: Pull complete 3a12d6e582e7: Pull complete 0a3d98d81a07: Pull complete 43b3a182ba00: Pull complete Creating monitor_filebeat ... done Creating skale_transaction-manager ... done Creating skale_watchdog ... done Creating skale_admin ... done Creating skale_bounty ... done Creating skale_api ... done ``` You can verify installation procedure by running: ```shell theme={null} sudo skale wallet info ``` **Output:** ```shell theme={null} Address: ETH balance: 1.0 ETH SKALE balance: 0 SKALE ``` The common problem is network misconfiguration between the supernode and SGXWallet. You can recheck connection status using `skale health sgx`: ```shell theme={null} sudo skale health sgx ``` **Output:** ```shell theme={null} SGX server status: ┌────────────────┬──────────────────────────┐ │ SGX server URL │ │ ├────────────────┼──────────────────────────┤ │ Status │ CONNECTED │ └────────────────┴──────────────────────────┘ ``` 22. **Disable SGX wallet autosign** Switch back to sgx server and go to `sgxwallet/run_sgx` folder ```shell theme={null} cd sgxwallet/run_sgx ``` Remove `-s` option in docker-compose.yml ```shell theme={null} sed -i -E 's/(command: .*) -s( .*)?$/\1\2/g' docker-compose.yml ``` Restart the container ```shell theme={null} docker compose down && docker compose up -d ``` 23. **Setup SSL Certificates** You will need to setup redirects from each supernode IP to the supernode domain, and issue SSL certs from a Trusted CA. You can issue a separate certificate for each subdomain (node-0.awesome-validator.com, node-1.awesome-validator.com) or issue a single Wildcard SSL for all supernodes (\*.awesome-validator.com). As a result, you should have 2 main files saved and copied to the respective supernodes: * Certificate file (for example, fullchain.pem or cert.pem) * Private key file (for example, privkey.pem, pk.pem) Upload certificates to the SKALE Node: ```shell theme={null} sudo skale ssl upload -c $PATH_TO_CERT_FILE -k $PATH_TO_KEY_FILE ``` Check SSL status: ```shell theme={null} sudo skale ssl status ``` For more details, please see [Node SSL docs](/validators/node-cli/node-ssl-setup). 24. **Fund Node wallet with ETH** Some of the supernode operations send ETH mainnet transaction (e.g. chain creation). So the supernode wallet should have at least 1 ETH To get the address you should run `skale wallet info` command. Spent ETH is reimbursed after the transaction was completed. 25. **Sign Validator ID using SGXWallet** Using *validator-cli* check your validator ID: ```shell theme={null} sk-val validator ls ``` Get your SKALE supernode signature by running Node CLI command. ```shell theme={null} sudo skale node signature [VALIDATOR_ID] ``` **Output:** ```shell theme={null} Signature: ``` 26. **Link SKALE wallet address to your validator account using Validator CLI** To successfully register new supernode you should bind supernode address and validator entity using *validator-cli* `link-address`: ```shell theme={null} sk-val validator link-address [NODE_ADDRESS] [SIGNATURE] ``` You can find supernode address by executing `skale wallet info` command Optional arguments: * `--pk-file` - Path to file with private key (only for `software` wallet type) * `--gas-price` - Gas price value in Gwei for transaction (if not specified doubled average network value will be used) * `--yes` - Confirmation flag 27. **Backup Node** We strongly recommend to regularly backup supernode data. The critical information stored `~/.skale` directory. The `skale node backup` command archives the data which you can download and store somewhere else. To restore the supernode you should use `skale node restore` More information can be found [here](/validators/node-cli/releases/v2-0#node-backup). 28. **Accept Delegations** Every delegation need to be accepted. You can do it using `sk-val validator accept-delegation` command: ```shell theme={null} sk-val validator accept-delegation --delegation-id [DELEGATION-ID] ``` Required arguments: * `--delegation-id` - Delegation id to accept Optional arguments: * `--pk-file` - Path to file with private key (only for software wallet type) * `--gas-price` - Gas price value in Gwei for transaction (if not specified doubled average network value will be used) * `--yes` - Confirmation flag You can get \[DELEGATION-ID] by running `sk-val validator delegations`: ```shell theme={null} sk-val validator delegations [VALIDATOR_ID] ``` You will see your pending delegation (`PENDING` status) as well as already accepted ones (`DELEGATED` status). To register the supernode validator must meet the Minimum Staking Requirement (MSR) of 20,000,000 SKL. ## Register Node in SKALE Network 29. **Register Node with Node CLI** Before proceeding, you will need to have at least **1 ETH**. Also amount of delegated skale tokens need to be more or equal to minimum staking amount. To register with the network, you will need to provide the following: * Node name * Machine public IP * Domain name ```shell theme={null} sudo skale node register --name [NODE_NAME] --ip [NODE_IP] --domain [DOMAIN_NAME] ``` Optional arguments: * `--port` - beginning of the port range that will be used for skale SKALE Chains (`10000` by default) **Output:** ```shell theme={null} Node registered in SKALE manager. For more info run: skale node info ``` 30. **Check Node Status** You can check the status of your supernode, and ensure that it's properly registered with the SKALE Network. ```shell theme={null} sudo skale node info ``` **Output:** ```shell theme={null} # Node info Name: ID: IP: Public IP: Port: Domain name: Status: Active ``` ## Post Registration Checks 31. **Post Registration Checks** * [ ] Private and backup keys are secured in a safe place. * [ ] VPN is configured on all SGXWallet servers. * [ ] Ensure node wallets have sufficient ETH. * [ ] Accept delegations for the next month. * [ ] Check telegram notifications (if you enabled them). * [ ] Use [watchdog](/validators/watchdog/overview) to monitor node status. * [ ] Get support from the SKALE validator community. # Run Sync Node Source: https://docs.skale.space/validators/run-sync-node Guide to running a SKALE Network Sync Node The Full-Sync Node is an available software implementation of a read-only SKALE Node that can help decrease the load on the core nodes of your chain. Applications are commonly read-heavy compared to writes, meaning they pull data from the chain more often than push them. For applications that contain many reads, a full-sync node can help increase chain stability and speed by reducing the number of actions the core nodes on the chain must do, leaving them with a more extraordinary ability to handle transactions. ## General Steps * Provision a machine * Notify core team of IP/IPs range (`x.x.x.x:x.x.x.x`) and SKALE Chain for full-sync. * Complete Full Sync setup ## Node Machine Requirements * A Linux x86\_64 machine * Ubuntu 22.04 * 8 vCPUs * 16GB RAM * 100GB root storage * 256GB attached storage for MEDIUM chain * 2TB attached storage for LARGE chain ## Node Network Requirements * Ports 80, 443, 3009, and 10000–18192, and ICMP IPv4 shouldn't be closed by external firewall ## Node Software Prerequisites * 16GB Swap * docker * docker-compose → 1.27.4 * iptables-persistent, btrfs-progs, lsof, lvm2, psmisc, and apt packages ## From Fresh Machine ```shell theme={null} sudo apt-get update # Install Docker and Docker-compose sudo apt-get install docker.io sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose # Install other dependencies sudo apt install iptables-persistent btrfs-progs lsof lvm2 psmisc # Setup Swapfile sudo fallocate -l 16G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile sudo cp /etc/fstab /etc/fstab.bak echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab ``` Before proceeding, notify the core team of the IP address of your full-sync node. It's suggested to use an elastic IP for the whitelisted IP address. ## Installation 1. ##### Download sync-node-cli tool ```shell theme={null} VERSION_NUM=2.6.2 && sudo -E bash -c "curl -L https://github.com/skalenetwork/node-cli/releases/download/$VERSION_NUM/skale-$VERSION_NUM-`uname -s`-`uname -m`-sync > /usr/local/bin/skale" ``` 2. ##### Apply executable permissions to the downloaded binary: ```shell theme={null} chmod +x /usr/local/bin/skale ``` 3. ##### Test the installation ```shell theme={null} skale --help ``` 4. ##### Prepare `.env` file: ```bash theme={null} CONTAINER_CONFIGS_STREAM="4.0.1" ENFORCE_BTRFS=True/False ENDPOINT=[[By Validator], YOUR GETH/INFURA ENDPOINT] MANAGER_CONTRACTS_ABI_URL="https://raw.githubusercontent.com/skalenetwork/concepts/refs/heads/master/releases/mainnet/skale-manager/1.12.0/skale-manager-1.12.0-mainnet-abi.json" IMA_CONTRACTS_ABI_URL="https://raw.githubusercontent.com/skalenetwork/concepts/refs/heads/master/releases/mainnet/IMA/2.2.0/mainnet/abi.json" DISK_MOUNTPOINT=[[By Validator], your attached storage /dev/sda or /dev/xvdd (this is an example. You just need to use your 2TB block device)] DOCKER_LVMPY_STREAM="1.0.2-stable.0" ENV_TYPE="mainnet" SCHAIN_NAME=[[By Validator], SKALE Chain NAME FOR YOUR SYNC NODE] ``` ENFORCE\_BTRFS=True formats your attached device as btrfs even if there is another filesystem. Backup the storage before running the command. 5. ##### Init sync node: ```shell theme={null} skale sync-node init [ENV_FILE_PATH] ``` 6. ##### Wait until your sync node will be inited 7. ##### After the node is successfully inited, wait until `skaled` will download the snapshot and starts catchup blocks (usually 15–30 minutes) ### Setup SSL certs Node SSL certificates support secure communication with SKALE Chain endpoints. By default, each node of a SKALE Chain listens on HTTP and WS ports. If SSL certs exist on the node, then HTTPS and WSS ports are also turned on. To upload SSL certs to the node run: ```shell theme={null} skale ssl upload -c $PATH_TO_CERT_FILE -k $PATH_TO_KEY_FILE ``` To check the status of your certs run: ```shell theme={null} skale ssl status ``` Once certs are uploaded, HTTPS/WSS endpoints should become available for your chain. ### Full sync node update Update full sync SKALE node on current machine ```shell theme={null} skale sync-node update [ENV_FILEPATH] ``` Options: * `--yes` - update without additional confirmation Arguments: * `ENV_FILEPATH` - path to env file where parameters are defined You can just update a file with environment variables used during `skale sync-node init`. ### Maintenance Maintenance commands are not available for a sync node. # Supernode Overview Source: https://docs.skale.space/validators/supernode-overview Overview of the SKALE Network Validators SKALE is a network of Layer-1 blockchains which have zero gas fees, infinite scalability, and are built for best-in-class UX. SKALE is unique from other blockchain networks. The SKALE Network has a pool of validators that work together to secure the network. These validators provide computation power to the SKALE Network by deploying SKALE supernodes. The collection of validators and the supernode(s) they spin up represent the entire validator network that performs work for SKALE Chains (a Layer-1 Ethereum Compatible Chain). Each SKALE Chain in the network is operated by a group of virtualized nodes selected from a subset of the supernodes run by validators, utilizing either all or part of the supernode's computational and storage resources \[multi-tenancy]. As each supernode in the network continues to participate in their assigned SKALE Chains, they are awarded bounties based on their performance at the end of each network epoch. Each supernode is monitored by their peer supernode. When a SKALE Chain has reached the end of its lifetime, the resources (computation, storage) of its virtualized nodes will be freed so that validator supernodes may participate in newly created SKALE Chains. Validator supernodes also receive direct payment from SKALE Chain pricing, which is "rent" paid by the chain owners/operators for this compute. ## SKALE Economics Please explore the following links to learn more about the SKALE Token Economics: * [SKALE Validator & Delegator Token Economics](https://skale.network/blog/validator-economics) * [Economics FAQ](https://skale.network/blog/skale-update-economics-faq) * [Metrics that matter blog](https://skale.network/blog/metrics-that-matter-for-token-launches) * [Tokenomics one-pager](https://skale.network/tokenomics) * [Token page](https://skale.network/token) ## Expectations * Reliable connection to SKALE Network (low latency, high uptime) * Robust security practices and network architecture * Running the latest SKALE Node software releases * Participation in governance (when applicable) To promote proper behavior, SKALE uses a Proof of Stake (PoS) system, requiring each supernode to stake a set amount of SKALE tokens, which can be slashed for any network-prohibited activity. Activities not approved by the network include those which denote a failure to properly participate in each assigned SKALE Chain’s consensus, and maintain uptime and latency standards enforced by the Service Level Agreement (SLA). Network SLAs are enforced through an algorithmic peer review system whereby each supernode is appointed 24 supernode peers to monitor and log their network participation, uptime, and latency. These metrics will be collected and averaged on the Ethereum mainnet to reward or slash supernodes according to their respective performance. ## Requirements To be added as a supernode to the SKALE Network, a prospective supernode must run the SKALE Admin, which manages all operations in the supernode and is installed with the [SKALE Node CLI](/validators/node-cli/overview). SKALE Admin evaluates the prospective supernode to ensure that it's upholding network hardware requirements. If the prospective supernode passes this verification step, the SKALE Admin permits the supernode to submit a request to SKALE Manager to join the network. This request contains both the required network deposit as well as supernode metadata collected by the SKALE Daemon (for example IP address, port, public key, etc.). Once the request has been submitted to SKALE Manager on Ethereum, SKALE Manager randomly defines the prospective supernode as a 'full node' or a 'fractional node' by assigning different sizes of SKALE Chains to the prospective supernode. Full nodes have all their resources utilized for a single SKALE Chain, while fractional nodes participate in multiple SKALE Chains (multi-tenancy). ### General Hardware Requirements **Important notes:** If you have any issues, you can save the logs using the `skale logs dump` command. It's also useful to check logs from node-cli using `skale cli logs` from the docker plugin at `/var/log/docker-lvmpy/lvmpy.log` if there are any issues. ### Block Storage Docker has an easy way of limiting other machine resources such as CPU, memory, and volume. These resources are configurable through [the docker setup](https://docs.docker.com/config/containers/resource_constraints/?source=post_page-----9859682f4147----------------------). Configuring machine resources such as CPU and memory is easy to complete via the docker setup; however, configuring volume requires a few more steps. To assist with configuring volume, SKALE Labs introduced [docker-lvmpy](https://github.com/skalenetwork/docker-lvmpy), a simple volume driver for Logical Volume Manager (LVM) volumes written in Python to format and partition disk space per SKALE Chain. When a validator sets up a supernode through the CLI, SKALE Admin calls docker-lvmpy to format the external and unmounted volume. Each validator in the SKALE Network must provide a non-boot external disk space, which will be used to partition the volume by the SKALE Admin. ![SKALE Admin Flow](https://assets.website-files.com/5be05ae542686c4ebf192462/5d9ce199ca4f18fa76e29ca0_Screen%20Shot%202019-10-08%20at%2012.19.30%20PM.png) ![SKALE Admin Flow 2](https://assets.website-files.com/5be05ae542686c4ebf192462/5d9ce198d4f7a4dcff8cd609_Screen%20Shot%202019-10-08%20at%2012.19.46%20PM.png) SKALE Admin calls docker-lvmpy to limit disk space per container (for example, 20GB) and splits the volume into 1/128 size partitions. Once LVM splits the container and allocates the new space to the SKALE Chain, docker-lvmpy informs SKALE Admin that the disk is limited based on the configured SKALE Chain size. ### SGX (HSM) Software Guard Extension (SGX) is an Intel® technology coprocessor ledger used for memory encryption and increasing the security of application code by storing encrypted data in the processor. A SKALE node connects to the SGX wallet server, and then the SGX wallet connects to the wallet enclave (BLS signatures and ETH keys). Keys stored in the processor can't be taken out, and they can only be encrypted or decrypted through the enclave. The key within the processor is secure in the enclave, and hackers can't hack the enclave and receive the key. Without SGX, human confirmation is necessary for transactions made in the SKALE Network. SKALE uses SGX for securing cryptographic keys by allowing supernodes to connect hardware wallets without human interaction. SKALE uses BLS to sign blocks in consensus, and ECDSA is needed to validate regular Ethereum transactions. SKALE will have a separate wallet integration for ECDSA. SKALE will use Intel® SGX technology to store BLS signatures at the coprocessor level and let users automatically authorize transactions. Currently, other than bare-metal servers, SGX is supported for these cloud providers: * [Alibaba Cloud](https://www.alibabacloud.com/help/doc-detail/108507.html?spm=a2c5t.10695662.1996646101.searchclickresult.84d1a80dPBX0Di) * [Equinix](https://www.equinix.com/services/edge-services/smartkey/) * [IBM Cloud Data Guard](https://www.ibm.com/cloud/blog/data-use-protection-ibm-cloud-using-intel-sgx?mhsrc=ibmsearch_a\&mhq=sgx) * [Microsoft Azure](https://www.intel.com/content/www/us/en/developer/articles/technical/microsoft-azure-confidential-computing-with-intel-sgx.html) (HSMs) Ledger Nano, or Trezor can support ECDSA signatures but not BLS signatures at this time, which are used by SKALE Consensus. **Advantages of SGX Wallet** * Helps developers protect sensitive data * Programmable for advanced crypto, such as BLS signatures * Doesn't require validators to continually confirm transactions * All SKALE crypto (BLS/DKG) can be done through the SGX wallet SKALE will have two types of SGX operations: * **Local (Secure)**: Wallet running on the same server as the node * **Network**: Node talks to the SGX wallet over the SKALE Network. The validator is responsible for securing the connection. # SKALE Supernode Swap Limit Fix Source: https://docs.skale.space/validators/swap-limit-fix SKALE Supernode Swap Limit Fix for validators ## Introduction This guide provides instructions for SKALE validators to fix swap limit issues on their supernodes. We need to enable swap limiting capability so SKALE Chain containers can use swap properly. ## Background Swap memory is limited by default if memory for the container is limited. For each SKALE Chain container, 2.6GB of RAM is allocated on the machine, which means an additional 2.6GB will be allocated for swap. For more details, refer to the [Docker documentation on resource constraints](https://docs.docker.com/config/containers/resource_constraints/#--memory-swap-details). ## Checking Current Setup (Optional) You can check the current memory allocation for your SKALE Chain containers (if your supernode has SKALE Chains) using the following command: ```bash theme={null} docker inspect skale_schain_[SKALE Chain-NAME] | grep Memory ``` A correct setup should look similar to this: ``` "Memory": 2678032302, "MemoryReservation": 0, "MemorySwap": 5356064604, "MemorySwappiness": null, ``` `MemorySwap` should be x2 of `Memory` value. ## Identifying the Issue The issue occurs when the swap limit capability is disabled in the kernel. To check if this is the case, run: ```bash theme={null} docker info ``` If you see the following line at the end of the output, it means the swap limiting capability is disabled: ``` WARNING: No swap limit support ``` ## Fixing the Swap Limit Issue If you will encounter with any issues during the following steps, please stop and contact SKALE team. If your supernode is running on Google Cloud, commands may differ. Please check discussions on [Stack Exchange](https://unix.stackexchange.com/a/495058) or contact SKALE team. Follow these steps to enable swap limiting capability: 1. Log into the supernode as a user that you used to setup the supernode (user should have sudo privileges). 2. Make a backup of your supernode and move backup archive to another machine: ```bash theme={null} skale node backup [BACKUP_FOLDER_PATH] [ENV_FILE] ``` Check out [node-cli backup docs](https://github.com/skalenetwork/node-cli?tab=readme-ov-file#node-backup) for more details. 3. Turn off your SKALE Supernode: ```bash theme={null} skale node turn-off ``` Wait for the supernode to turn off gracefully. 4. Edit the `/etc/default/grub` file. Add or edit the `GRUB_CMDLINE_LINUX` line to include the following key-value pairs: ``` GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" ``` 5. Update GRUB: ```bash theme={null} sudo update-grub ``` 6. Reboot the machine: ```bash theme={null} sudo reboot ``` 7. After the machine has rebooted, turn your SKALE Supernode back on: ```bash theme={null} skale node turn-on [ENV_FILEPATH] ``` Replace `[ENV_FILEPATH]` with the path to your environment file (the same you used for supernode updates). For more details on this process, you can refer to this [Unix Stack Exchange thread](https://unix.stackexchange.com/questions/342735/docker-warning-no-swap-limit-support). 8. After the Supernode is turned on, check if the swap limit is enabled: ```bash theme={null} docker info ``` No warning should be displayed. # Validator Troubleshooting Source: https://docs.skale.space/validators/troubleshooting Asynchronous Support for Validators Need help solving an issue? Check to see if this has already been answered below. If you can't find an answer to your issue, reach out to the SKALE Network developer community on [Discord](https://discord.com/invite/gM5XBy6). See [Changing Node IP](/validators/node-cli/changing-node-ip) Validators are responsible for backing up everything on the supernode. SKALE Network relies on validators to complete the backup procedure for each of the supernodes. SKALE Network replicates the data for each SKALE Chain across 16 nodes, adding an additional layer of data availability. If a node goes down for a long time, the other 2/3 of 15 will be responsible for taking the SKALE Chain's snapshot and shuffling the SKALE Chain in the SKALE Network. If a validator supernode is down for a long time, it may be reallocated to service new SKALE Chains depending on the length of the downtime. Please check [supernode backup](https://github.com/skalenetwork/skale-node-cli/blob/develop/README.md#node-commands) and [sgx\_backup](https://github.com/skalenetwork/sgxwallet/blob/develop/docs/admin/backup-procedure.md) to learn more about how to back up the supernode or SGXWallet, and restore. You can find these details in the [skale-node](https://github.com/skalenetwork/skale-node) repository and the [docker-compose.yml](https://github.com/skalenetwork/skale-node/blob/f928b95e69c548f12b4b21bd11a16fe2d239b83b/docker-compose.yml). You can use the node-cli to view firewall info and SKALE Chain info: ```shell theme={null} # Show firewall rules for IP and Ports skale SKALE Chains show-rules # Show the SKALE Chain Config skale SKALE Chains config [SCHAIN_NAME] ``` In this file, you can see all the SKALE Chain information: ![SKALE Chain Info](https://assets.website-files.com/5be05ae542686c4ebf192462/5d9f9cb5adfc337b00747f66_Screen%20Shot%202019-10-10%20at%201.53.21%20PM.png) | Port | Description | Notes | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 3009/TCP | Watchdog service provides sla-agent and SKALE Monitoring/Metrics site with data about all containers' statuses on all SKALE supernodes | Used by nginx as reverse proxy for Flask-based Watchdog | | 8080/TCP | Used by cAdvisor (Prometheus exporter) - to analyze and expose resource usage and performance data from running docker containers | Running cAdvisor container is optional (MONITORING\_CONTAINERS=True) | | 9100/TCP | Used by Prometheus Node Exporter - to measure host resources such as memory, disk, and CPU utilization, etc. | Running Node exporter container is optional (MONITORING\_CONTAINERS=True) | | 22 | Used by the supernode operator to ssh into the machine | | | 10000–11500/TCP | These ports should be open for communications inside SKALE Chains between different supernodes. | When there are no SKALE Chains, these ports are closed by iptables rules. It’s important not to add another firewall, because SKALE Chain configuration is created dynamically as well as iptables rules. | Yes. However, you will need to make sure that your platform is compatible with SKALE. In particular, your platform must allow docker-lvmpy to dynamically partition the external drive for each SKALE chain. Validators can run these commands to check their logs in their supernode: Here are some logs and commands you can use for troubleshooting or to provide logs to the core team. The most used and first place to look at: ```shell theme={null} docker logs skale_api ``` or ```shell theme={null} docker logs skale_admin ``` Transaction manager logs: ```shell theme={null} docker logs skale_transaction_manager ``` All running containers logs: ```shell theme={null} skale logs dump [PATH] ``` SGX certification in validator supernode: ```shell theme={null} ls -l ~/.skale/node_data/sgx_certs/ ``` SGX wallet logs: ```shell theme={null} docker logs runsgx_sgxwallet_1 ``` Node-cli debugging logs: ```shell theme={null} ~/.skale/.skale-cli-log/debug-node-cli.log ``` Docker-lvmpy logs: ```shell theme={null} cat /var/log/docker-lvmpy/lvmpy.log ``` SGXWallet may crash after a reboot if you didn't disable automatic updates. This is because SGXWallet is based on new low-level technology, and kernel updates may break the system. It's recommended to only update the SGXWallet server if there are critical security fixes. Updating system packages is an important part of supernode maintenance and security. Updates may contain fixes for recent security issues. However, newer updates may be incompatible with SKALE supernode software. Therefore, updating should be executed carefully. Make sure that the infrastructure provider doesn't update packages after the system is restarted. The following list contains packages that require additional attention. Critical risk (updates should be performed only if there are some messages from the core team): * kernel updates on SGXWallet server. Currently, SGX is tested on 4.15-\* kernels. It's best to avoid minor version updates too. **High Risk** (updates should be avoided in general): * kernel on SKALE supernode server * docker * docker-compose * btrfs-progs **Medium Risk** (updates should be performed very carefully): * lvm2 * iptables * iptables-persistent * python **General tips** 1. Carefully check which packages will be updated when executing `apt upgrade`. You can use `apt list --upgradable` 2. Avoid executing `apt dist-upgrade` 3. Disabling updates for certain packages can be done using `apt-mark hold` ([apt-mark man page](https://manpages.debian.org/stretch/apt/apt-mark.8.en.html)). For example, to forbid kernel updates you should run: ```shell theme={null} sudo apt-mark hold linux-generic linux-image-generic linux-headers-generic ``` Another option is to use unattended upgrades ([Unattended Upgrades wiki](https://wiki.debian.org/UnattendedUpgrades)), which allows you to automatically keep the system current with the latest security updates. There is an option to exclude some packages from the update list using the `Package-Blacklist` section in the configuration file. For example, to disallow Linux kernel updates, you should place the following lines in the `/etc/apt/apt.conf.d/50unattended-upgrades` file: ```shell theme={null} Unattended-Upgrade::Package-Blacklist { "linux-generic"; "linux-image-generic"; "linux-headers-generic"; }; ``` SKALE Network has many resources designed to help you get your questions answered. You can reach out to the SKALE Network developer community on Discord, or submit a support request below. [Contact Support](https://skalelabs.typeform.com/to/pSu895) # Validator CLI Source: https://docs.skale.space/validators/validator-cli/overview The Validator CLI offers key tools for SKALE Network validators On the **SKALE Network**, the **validator CLI** (Command Line Interface) is a tool used by validators to interact with the network and manage their validator supernodes. Validators play a crucial role in validating transactions, producing blocks, and maintaining the security of the network. Below are some of the main functions of the validator CLI: ## Node Management Validators can use the CLI to: * Start, stop, and monitor their supernodes on the SKALE Network. ## Staking Operations Validators can: * Stake SKALE tokens (SKL) or manage their delegations through the CLI. This is essential for securing the network and earning rewards. ## Upgrades and Updates The CLI is useful for: * Applying software upgrades to validator supernodes. * Managing updates to keep supernodes compliant with the latest network features. ## Validator Registration Validators can: * Register their supernodes on the SKALE Network, enabling them to participate in the consensus process. ## Monitoring and Debugging The CLI allows validators to: * Monitor the performance of their supernodes. * Troubleshoot and access logs for debugging purposes. Validators rely on the **validator CLI** to ensure smooth operations and proper participation in SKALE's decentralized and elastic blockchain infrastructure. # Validator CLI - v1.2.0 Source: https://docs.skale.space/validators/validator-cli/releases/v1-2-0 Validator CLI v1.2.0 is the latest release ## Installation **Requirements:** * Linux x86\_64 machine * Access the latest version at [https://github.com/skalenetwork/validator-cli/releases](https://github.com/skalenetwork/validator-cli/releases) 1. Download the executable ```shell theme={null} VERSION_NUM={put the version number here} && sudo -E bash -c "curl -L https://github.com/skalenetwork/validator-cli/releases/download/$VERSION_NUM/sk-val-$VERSION_NUM-`uname -s`-`uname -m` > /usr/local/bin/sk-val" ``` 2. Apply executable permissions to the binary ```shell theme={null} sudo chmod +x /usr/local/bin/sk-val ``` ## CLI Usage ## Initialize Validator CLI Download SKALE Manager contracts info and set the supernode of the network ```shell theme={null} sk-val init ``` **Required Arguments** * `--endpoint/-e` is the RPC endpoint of the supernode in the network where SKALE Manager is deployed \[http or https] * `--contracts-url/-c` is the URL to SKALE Manager contracts ABI and addresses * `-w/--wallet` is the type of the wallet that will be used to sign transactions. Must be of type software, sgx, or hardware. If you want to use the SGX wallet you need to initialize it first (see SGX commands). **Usage Example** ```shell theme={null} sk-val init -e ws://geth.test.com:8546 -c https://test.com/manager.json --wallet software ``` ## SGX Commands ### Init This command creates an SGX Wallet. ```shell theme={null} sk-val sgx init [SGX_SERVER_URL] ``` **Optional Arguments** * `--force/-f` rewrites the current sgx wallet data * `--ssl-port` specifies the port that is used by the sgx server to establish a TLS connection ### Info This command prints information about the SGX Wallet. ```shell theme={null} sk-val sgx info ``` **Optional Arguments** * `--raw` prints info in plain JSON ## Validator Commands ### Register Registers as a new SKALE validator. ```shell theme={null} sk-val validator register ``` **Required Arguments** * `--name/-n` is the validator name * `--description/-d` is the validator description * `--commission-rate/-c` is the commission rate in percentage form * `--min-delegation` is the minimum delegation amount that the validator will accept **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance #### Usage Example ```shell theme={null} sk-val validator register -n test -d "test description" -c 20 --min-delegation 1000 --pk-file ./pk.txt ``` ### List Lists all available validators. ```shell theme={null} sk-val validator ls ``` **Optional Arguments** * `--wei/-w` can be used to show the amount of tokens in wei ### Delegations Returns a list of delegations for a given validator id. ```shell theme={null} sk-val validator delegations [VALIDATOR_ID] ``` **Required Parameters** 1. `VALIDATOR_ID` is the id of the validator. This is a numeric value. ### Accept Pending Delegations This command will accept pending delegations by delegation Id. ```shell theme={null} sk-val validator accept-delegation --pk-file ./pk.txt ``` **Requirement Arguments** * `--delegation-id` is the id of he delegation request to accept **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Accept All Pending Delegations This will accept ALL pending delegations associated with this validator address. A list with all pending delegations will be shown first. The executor will need to confirm the operation. ```shell theme={null} sk-val validator accept-all-delegations ./pk.txt ``` **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. ### Validator Linked Addresses Lists the linked addresses for the validator address. ```shell theme={null} sk-val validator linked-addresses [ADDRESS] ``` **Required Parameters** 1. `ADDRESS` is the Ethereum address of the validator ### Link Address Links a supernode address to the validator account. ```shell theme={null} sk-val validator link-address [ADDRESS] [NODE_SIGNATURE] --pk-file ./pk.txt ``` **Required Parameters** 1. `ADDRESS` is the Ethereum address that will be linked 2. `NODE_SIGNATURE` is the signature of the supernode that you can get using the `skale node signature` command from the SKALE Node CLI **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Unlink Address Unlinks a supernode address from the validator account. ```shell theme={null} sk-val validator unlink-address [ADDRESS] --pk-file ./pk.txt ``` **Required Parameters** 1. `ADDRESS` is the Ethereum address that will be unlinked **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Validator Info Shows info about a given validator. ```shell theme={null} sk-val validator info [VALIDATOR_ID] ``` **Required Parameters** 1. `VALIDATOR_ID` is the validator id to lookup **Output Includes:** 1. Validator Id 2. Validator Name 3. Validator Address 4. Validator Fee Rate (in percentage form) 5. Minimum Delegation Amount (SKL) 6. If the validator is accepting new delegation requests ### Withdraw Fee Withdraws earned fees to a specified address. ```shell theme={null} sk-val validator withdraw-fee [RECIPIENT_ADDRESS] --pk-file ./pk.txt ``` **Required Parameters** 1. `RECIPIENT_ADDRESS` is the address to transfer the bounties too **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Set Minimum Delegation Amount (MDA) Sets new minimum delegation amount for the validator. ```shell theme={null} sk-val validator set-mda [NEW_MDA] --pk-file ./pk.txt ``` **Required Parameters** 1. `NEW_MDA` is the new mda value **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Request Address Change Requests a change of Ethereum Address for the validator. ```shell theme={null} sk-val validator change-address [ADDRESS] --pk-file ./pk.txt ``` **Required Parameters** 1. `ADDRESS` is the validator Ethereum address **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Confirm Address Change Confirms a requested address change for the validator. ```shell theme={null} sk-val validator confirm-address [VALIDATOR_ID] --pk-file ./pk.txt ``` **Required Parameters** 1. `VALIDATOR_ID` is the ID of the associated validator **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` the confirmation flag may be set in advance ### Earned Fees Retrieves the earned fee amount for the validator address. ```shell theme={null} sk-val validator earned-fees [ADDRESS] ``` \**Required Parameters* 1. `ADDRESS` is the validator address to check **Optional Arguments** * `--wei` can be used to show the amount in wei ## Holder Commands ### Delegate Delegate tokens to a validator ```shell theme={null} sk-val holder delegate ``` **Required Arguments** * `--validator-id` is the id of the validator to delegate to * `--amount` is the amount of SKL tokens to delegate * `--delegation-period` is the delegation period in months \[Set to 2] * `--info` to request info on the delegation **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. ### Delegations Use to list out all delegations for an address. ```shell theme={null} sk-val holder delegations [ADDRESS] ``` **Required Parameters** 1. `ADDRESS` is the Ethereum address of the SKL token holder **Optional Arguments** * `--wei/-w` to show amounts in wei ### Cancel Pending Delegation Use to cancel a pending delegation request. ```shell theme={null} sk-val holder cancel-delegation [DELEGATION_ID] ``` **Required Parameters** 1. `DELEGATION_ID` is the id of the delegation to cancel **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. ### Request Undelegation Request undelegation which goes into affect at the end of the undelegation period. ```shell theme={null} sk-val holder undelegate [DELEGATION_ID] ``` **Required Parameters** 1. `DELEGATION_ID` is the id of the delegation to cancel **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. ### Withdraw Bounty Withdraws a bounty to the specified address ```shell theme={null} sk-val holder withdraw-bounty [VALIDATOR_ID] [RECIPIENT_ADDRESS] --pk-file ./pk.txt ``` **Required Parameters** 1. `VALIDATOR_ID` is the id of the validator 2. `RECIPIENT_ADDRESS` is the Ethereum address to have the bounty sent too **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--gas-price` allows the executor to specify the gas price value in gwei for a transaction. If not specified, the average gas price of the network will be double and set. * `--yes` confirmation flag can be used to auto execute ### Locked Shows the amount of locked tokens for an address. ```shell theme={null} sk-val holder locked [ADDRESS] ``` **Required Parameters** 1. `ADDRESS` is the Ethereum address of the SKL token holder **Optional Arguments** * `--wei/-w` to show amounts in wei ### Earned Bounties Reads the amount of earned bounties by a token holder for a single validator. ```shell theme={null} sk-val holder earned-bounties [VALIDATOR_ID] [ADDRESS] ``` **Required Parameters** 1. `VALIDATOR_ID` is the id of the validator 2. `ADDRESS` is the Ethereum address of the SKL token holder **Optional Arguments** * `--wei/-w` to show amounts in wei ## Wallet Commands ### Setup Ledger This only works if you are using a **Ledger Wallet**. ```shell theme={null} sk-val wallet setup-ledger ``` **Required Arguments** * `--address-index` is the index of the wallet to use (starts at 0) * `--keys-type` is the type of Ledger keys whether live or legacy ### Send ETH Tokens Executes a transfer of ETH tokens to a specific address. ```shell theme={null} sk-val wallet send-eth [ADDRESS] [AMOUNT] ``` **Required Parameters** 1. `ADDRESS` is the Ethereum receiver address (e.g to) 2. `AMOUNT` is the amount of ETH tokens to send **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--yes` confirmation flag can be used to auto execute #### Usage Example ```shell theme={null} sk-val wallet send-eth 0x01C19c5d3Ad1C3014145fC82263Fbae09e23924A 0.01 --pk-file ./pk.txt --yes ``` ### Send SKL Tokens Executes a transfer of SKL tokens to a specific address. ```shell theme={null} sk-val wallet send-SKL [ADDRESS] [AMOUNT] ``` **Required Parameters** 1. `ADDRESS` is the Ethereum receiver address (e.g to) 2. `AMOUNT` is the amount of ETH tokens to send **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--yes` confirmation flag can be used to auto execute #### Usage Example ```shell theme={null} sk-val wallet send-SKL 0x01C19c5d3Ad1C3014145fC82263Fbae09e23924A 0.01 --pk-file ./pk.txt --yes ``` ## Self-Recharging Wallet Commands ### Balance Shows the balance of the validator self-recharging wallet. ```shell theme={null} sk-val srw balance [VALIDATOR_ID] ``` **Required Parameters** 1. `VALIDATOR_ID` is the ID of the validator **Optional Arguments** * `--wei/-w` shows the amount in wei #### Usage Example ```shell theme={null} sk-val srw balance 1 --wei ``` ### Recharge Wallet Recharges the validator SRW wallet (amount in ETH). ```shell theme={null} sk-val srw recharge [AMOUNT] ``` **Required Parameters** 1. `AMOUNT` is the amount of ETH tokens to send **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--yes` confirmation flag can be used to auto execute #### Usage Example ```shell theme={null} sk-val srw recharge 0.1 --pk-file ./tests/test-pk.txt ``` ### Withdraw Withdraw ETH from validator SRW (amount in ETH). ```shell theme={null} sk-val srw withdraw [AMOUNT] ``` **Required Parameters** 1. `AMOUNT` is the amount of ETH tokens to send **Optional Arguments** * `--pk-file` is a path to a file with private key (only supported for *software* wallet type) * `--yes` confirmation flag can be used to auto execute #### Usage Example ```shell theme={null} sk-val srw withdraw 0.1 --pk-file ./tests/test-pk.txt ``` ## Exit Codes | Code | Explanation | | ---- | ----------------------- | | 0 | Everything is OK | | 1 | General error exit code | | 3 | Bad API response | | 4 | Script execution error | | 5 | Transaction error | | 6 | Revert error | # Self Recharging Wallets Source: https://docs.skale.space/validators/validator-cli/self-recharging-wallets Deep dive into self-recharging capabilities of supernode wallets ## Types of Wallets There are several types of wallets in the SKALE Network. These include: 1. Wallets Controlled by the validator, supernode operator, and SKALE Chain owner. These wallets may be software or hardware wallets. 2. Self-Recharging wallets can be for validators and for the SKALE Chain owner. ## Node Transactions & Reimbursement The following table explores what supernode transactions are reimbursed and by who. | Transaction | Reimbursed By | Description of Transaction | | ------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | getBounty() | Validator | Called every month by each supernode to request bounty. | | nodeExit() | Validator | To remove a supernode from the network due to hardware failure or unmet MSR. | | complaint() | Validator | Performed in DKG failure mode when a supernode lodges a general DKG complaint against a malicious supernode who may have failed to send data. | | complaintBadData() | Validator | Performed in DKG failure mode when a supernode discovers that malicious data has been sent and requires verification data to validate. | | preResponse() | Validator | Performed in DKG failure mode, when a supernode sends a verification vector and secret key shares ahead of the final `response()` result. | | response() | Validator | Performed in DKG failure mode when a supernode sends a response to a `complaint()` to prove it's not malicious based on the data it sends in `preResponse()`. | | broadcast() | SKALE Chain Owner | Performed during SKALE Chain creation when distributed key generation process begins. Supernodes broadcast their secret key contributions to all other supernodes. | | alright() | SKALE Chain Owner | Performed during SKALE Chain creation when distributed key generation is in-process. Supernodes send receipts of secret key contributions. | ## Replenishment Self-recharging validator wallets can be replenished in ETH by anyone with the wallet's contract address and the `ValidatorId` or `sChainId`. This functionality allows anyone to ensure the continued ETH funding and operation of validator supernodes and SKALE Chains. If the *self-recharging*... * *validator balance* runs out of ETH, then the supernodes' wallets are first spent until transactions revert. * *SKALE Chain Owner balance* runs out of ETH, then transactions will revert. Node wallets connected to the self-recharging validator wallet are replenished automatically at the end of each transaction based on the best estimate of the gas consumed in the transaction. If the validator self-recharging wallet is out of ETH, then supernode wallets start using their own ETH until the supernode balance is 0. Suppose the validator self-recharging wallet has 0 balance at the same time that the supernode wallet balance is 0. In that case, validators are then responsible for recharging both the validator self-recharging wallet and the supernode wallets with 0 balance. If a supernode's `getBounty` transaction reverts and can't complete the transaction before the end of the month, the validator-delegators will receive less bounty. Validators must ensure sufficient balance and a working endpoint to connect with Ethereum mainnet. There is no easy way to know the EVM's refund counter's value from inside the EVM. Thus, refunds may offset the transaction's final net cost, and supernode balances may slightly increase over time. This process will be reviewed and improved over time. ## Using a Self-Recharging Wallet When a new validator is registered, a self-recharging wallet is automatically deployed. The validator's private key has withdraw access to this wallet. Validators can use this private key to close the self-recharging wallet should the validator leave the network. As a validator links supernodes to their validator ID, these linked supernodes will automatically request reimbursement from the validator's self-recharging wallet for those transactions. If a supernode is unlinked from the validator, this supernode will no longer request reimbursement from the validator's self-recharging wallet. Existing validators will automatically be added to the Wallet contract and will start using them once this feature is deployed to mainnet. ## Interacting with the Self-Recharging Wallet (SRW) Validators can use the CLI to recharge, withdraw funds from, or check the self-recharging wallet's balance. Anyone can recharge or check the balance by interacting with the Wallet contract on Ethereum. *For the CLI* ```shell theme={null} # Validator recharges the self-recharging wallet from their wallet (sk-val wallet info) sk-val srw recharge AMOUNT_IN_ETH [--validator-id VALIDATOR_ID] [--gas-price PRICE_IN_GWEI] [--pk-file PATH_TO_PK] > sk-val srw recharge 0.1 # Recharge using a hardware wallet > sk-val srw recharge 0.1 --validator-id 1 # Recharge validator ID 1 using a hardware wallet > sk-val srw recharge 0.1 --pk-file ./tests/test-pk.txt # Recharge using a software wallet # Validator withdraws an amount from the self-recharging wallet sk-val srw withdraw AMOUNT_IN_ETH [--pk-file PATH_TO_PK] [--gas-price PRICE_IN_GWEI] > sk-val srw withdraw 0.1 # Withdraw using a hardware wallet > sk-val srw withdraw 0.1 --pk-file ./tests/test-pk.txt # Withdraw using a software wallet # Returns self-recharging wallet balance sk-val srw balance VALIDATOR_ID [--wei] > sk-val srw balance 1 # Show balance in ETH > sk-val srw balance 1 --wei # Show balance in Wei ``` *For Wallets Contract* See [https://github.com/skalenetwork/skale-manager/blob/develop/contracts/Wallets.sol](https://github.com/skalenetwork/skale-manager/blob/develop/contracts/Wallets.sol) ```shell theme={null} rechargeValidatorWallet(validatorId) withdrawFundsFromValidatorWallet(amount) getValidatorBalance(validatorId) ``` # Watchdog APIs Source: https://docs.skale.space/validators/watchdog/apis SKALE Watchdog APIs ## REST JSON APIs * [/status/core](#statuscore) * [/status/sgx](#statussgx) * [/status/SKALE Chains](#statusschains) * [/status/hardware](#statushardware) * [/status/endpoint](#statusendpoint) * [/status/SKALE Chain-containers-versions](#statusschain-containers-versions) * [/status/meta-info](#statusmeta-info) * [/status/btrfs](#statusbtrfs) * [/status/ssl](#statusssl) * [/status/ima](#statusima) * [/status/public-ip](#statuspublic-ip) * [/status/validator-nodes](#statusvalidator-nodes) ## Introduction SKALE Watchdog is microservice running on every SKALE supernode for providing SLA agents with supernode performance metrics. It also can be used for external monitoring goals (validators can use it in their monitoring tools). Watchdog is a Python script running in docker container with Flask web server that provides simple REST JSON API available on port 3009 (http\://YOUR\_SKALE\_NODE\_IP:3009). Currently, Watchdog can give data on all SKALE supernode docker containers, health checks of SKALE Chains and SGX server, ethereum supernode endpoint status, and hardware information. \[NOTE] Node CLI automatically opens port 3009 on the machine using iptables. Be sure that port 3009 is open for the supernode's external network for Watchdog to work. ## REST JSON APIs * [/status/core](#statuscore) * [/status/sgx](#statussgx) * [/status/SKALE Chains](#statusschains) * [/status/hardware](#statushardware) * [/status/endpoint](#statusendpoint) * [/status/SKALE Chain-containers-versions](#statusschain-containers-versions) * [/status/meta-info](#statusmeta-info) * [/status/btrfs](#statusbtrfs) * [/status/ssl](#statusssl) * [/status/ima](#statusima) * [/status/public-ip](#statuspublic-ip) * [/status/validator-nodes](#statusvalidator-nodes) ### /status/core #### Description Returns data on all SKALE containers running on a given node. These are containers with prefix `skale_`, including base containers and pairs of SKALE Chain and IMA containers, where every pair corresponds to a certain SKALE Chain assigned to this node. Here is a list of the base containers: * skale\_transaction-manager * skale\_admin * skale\_api * skale\_mysql * skale\_sla * skale\_bounty * skale\_watchdog * skale\_nginx Docker container name patterns for SKALE Chain with name SCHAIN\_NAME are the following: * SKALE Chain: skale\_schain\_SCHAIN\_NAME * IMA: skale\_ima\_SCHAIN\_NAME #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/core {"data": [{"image": "skalenetwork/ima:1.0.0-develop.103", "name": "skale_ima_clean-rigel-kentaurus", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 32501, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-08T18:03:23.165649145Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/SKALE Chain:3.2.2-develop.0", "name": "skale_schain_clean-rigel-kentaurus", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 32315, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-08T18:03:02.980981899Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/bounty-agent:1.1.1-beta.0", "name": "skale_bounty", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2834, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.745578956Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/admin:1.1.0-beta.7", "name": "skale_api", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2810, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.724467486Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/sla-agent:1.0.2-beta.1", "name": "skale_sla", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2831, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.75059756Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "nginx:1.19.6", "name": "skale_nginx", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2612, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.592144127Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "mysql/mysql-server:5.7.30", "name": "skale_mysql", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2367, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.363363602Z", "FinishedAt": "0001-01-01T00:00:00Z", "Health": {"Status": "healthy", "FailingStreak": 0, "Log": [{"Start": "2021-01-11T13:05:26.695580607Z", "End": "2021-01-11T13:05:26.7965889Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2021-01-11T13:05:56.8026356Z", "End": "2021-01-11T13:05:56.897819023Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2021-01-11T13:06:26.90380399Z", "End": "2021-01-11T13:06:27.00531651Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2021-01-11T13:06:57.011844463Z", "End": "2021-01-11T13:06:57.106312668Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2021-01-11T13:07:27.111509013Z", "End": "2021-01-11T13:07:27.218446754Z", "ExitCode": 0, "Output": "mysqld is alive\n"}]}}}, {"image": "skalenetwork/watchdog:1.1.2-beta.0", "name": "skale_watchdog", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2171, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.231188713Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/admin:1.1.0-beta.7", "name": "skale_admin", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 15922, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-08T15:30:06.84581235Z", "FinishedAt": "2021-01-08T15:30:06.61032202Z", "Health": {"Status": "healthy", "FailingStreak": 0, "Log": [{"Start": "2021-01-11T13:03:27.83704947Z", "End": "2021-01-11T13:03:27.943393521Z", "ExitCode": 0, "Output": "Modification time diff: 16.017173290252686, limit is 600\n"}, {"Start": "2021-01-11T13:04:27.948600024Z", "End": "2021-01-11T13:04:28.07052713Z", "ExitCode": 0, "Output": "Modification time diff: 30.681769371032715, limit is 600\n"}, {"Start": "2021-01-11T13:05:28.076286609Z", "End": "2021-01-11T13:05:28.18879886Z", "ExitCode": 0, "Output": "Modification time diff: 40.09002113342285, limit is 600\n"}, {"Start": "2021-01-11T13:06:28.194725277Z", "End": "2021-01-11T13:06:28.304819334Z", "ExitCode": 0, "Output": "Modification time diff: 4.169792890548706, limit is 600\n"}, {"Start": "2021-01-11T13:07:28.310191582Z", "End": "2021-01-11T13:07:28.432554349Z", "ExitCode": 0, "Output": "Modification time diff: 18.855625867843628, limit is 600\n"}]}}}, {"image": "skalenetwork/transaction-manager:1.1.0-beta.1", "name": "skale_transaction-manager", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2065, "ExitCode": 0, "Error": "", "StartedAt": "2021-01-05T18:59:01.201684713Z", "FinishedAt": "0001-01-01T00:00:00Z"}}], "error": null} ``` ### /status/sgx #### Description Returns SGX server info - connection status and SGX wallet version #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/sgx {"data": {"status": 0, "status_name": "CONNECTED", "sgx_wallet_version": "1.64.2"}, "error": null} ``` ### /status/SKALE Chains #### Description Returns list of health checks for every SKALE Chain running on a given node: * data\_dir - checks that SKALE Chain data dir exists * dkg - checks that DKG procedure is completed for current SKALE Chain * config - checks that SKALE Chain config file exists * volume - checks that SKALE Chain volume exists * firewall\_rules - checks that firewall rules are set correctly * container - checks that skaled container is running * ima\_container - checks that IMA container is running * RPC - checks that local skaled RPC is accessible * blocks - checks that local skaled is mining blocks #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/SKALE Chains {"data": [{"name": "clean-rigel-kentaurus", "healthchecks": {"data_dir": true, "dkg": true, "config": true, "volume": true, "firewall_rules": true, "container": true, "exit_code_ok": true, "ima_container": true, "rpc": true, "blocks": true}}], "error": null} ``` ### /status/endpoint #### Description Returns info on ethereum node endpoint, used by a given SKALE node - current block number and syncing status (web3.eth.syncing result) #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/endpoint {"data": {"block_number": 7917221, "syncing": false}, "error": null} ``` ### /status/hardware #### Description Returns node hardware information: * cpu\_total\_cores - return the number of logical CPUs in the system ( the number of physical cores multiplied by the number of threads that can run on each core) * cpu\_physical\_cores - return the number of physical CPUs in the system * memory - total physical memory in bytes (exclusive swap) * swap - total swap memory in bytes * system\_release - system/OS name and system’s release * uname\_version - system’s release version * attached\_storage\_size - attached storage size in bytes #### Usage ```shell theme={null} curl http://YOUR_SKALE_NODE_IP:3009/status/hardware {"data": {"cpu_total_cores": 8, "cpu_physical_cores": 8, "memory": 33675845632, "swap": 67 ``` #### Example of Response ```json theme={null} {"data": [{"image": "nginx:1.13.7", "name": "skale_nginx", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 18579, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:28.545487937Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/admin:1.1.0-beta.1", "name": "skale_api", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 18284, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.651007072Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/sla-agent:1.0.2-beta.1", "name": "skale_sla", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 18365, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.730205071Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/bounty-agent:1.1.1-beta.0", "name": "skale_bounty", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 18340, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.694385403Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/transaction-manager:1.1.0-beta.0", "name": "skale_transaction-manager", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 17872, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.25649668Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/watchdog:1.0.0-stable.0", "name": "skale_watchdog", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 18118, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.907066673Z", "FinishedAt": "0001-01-01T00:00:00Z"}}, {"image": "skalenetwork/admin:1.1.0-beta.1", "name": "skale_admin", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 17936, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.265352128Z", "FinishedAt": "0001-01-01T00:00:00Z", "Health": {"Status": "healthy", "FailingStreak": 0, "Log": [{"Start": "2020-12-15T14:04:29.314460489Z", "End": "2020-12-15T14:04:29.441963475Z", "ExitCode": 0, "Output": "Modification time diff: 21.14485740661621, limit is 600\n"}, {"Start": "2020-12-15T14:05:29.447580804Z", "End": "2020-12-15T14:05:29.580104983Z", "ExitCode": 0, "Output": "Modification time diff: 33.23975086212158, limit is 600\n"}, {"Start": "2020-12-15T14:06:29.586114183Z", "End": "2020-12-15T14:06:29.719576685Z", "ExitCode": 0, "Output": "Modification time diff: 0.5591189861297607, limit is 600\n"}, {"Start": "2020-12-15T14:07:29.727615313Z", "End": "2020-12-15T14:07:29.860632612Z", "ExitCode": 0, "Output": "Modification time diff: 13.140380859375, limit is 600\n"}, {"Start": "2020-12-15T14:08:29.866030839Z", "End": "2020-12-15T14:08:29.991292415Z", "ExitCode": 0, "Output": "Modification time diff: 25.21944308280945, limit is 600\n"}]}}}, {"image": "mysql/mysql-server:5.7.30", "name": "skale_mysql", "state": {"Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 17880, "ExitCode": 0, "Error": "", "StartedAt": "2020-12-15T13:48:27.270664629Z", "FinishedAt": "0001-01-01T00:00:00Z", "Health": {"Status": "healthy", "FailingStreak": 0, "Log": [{"Start": "2020-12-15T14:06:31.513600128Z", "End": "2020-12-15T14:06:31.628416403Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2020-12-15T14:07:01.635502928Z", "End": "2020-12-15T14:07:01.75593047Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2020-12-15T14:07:31.766279603Z", "End": "2020-12-15T14:07:31.88026375Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2020-12-15T14:08:01.885733506Z", "End": "2020-12-15T14:08:01.999542219Z", "ExitCode": 0, "Output": "mysqld is alive\n"}, {"Start": "2020-12-15T14:08:32.005145263Z", "End": "2020-12-15T14:08:32.115797294Z", "ExitCode": 0, "Output": "mysqld is alive\n"}]}}}], "error": null} ``` ### /status/containers Documentation unavailable. ### /status/meta-info #### Description Returns steam versions. #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/meta-info {"data": {"version": "1.1.0", "config_stream": "1.2.1", "docker_lvmpy_stream": "1.0.1-stable.1"}, "error": null} ``` ### /status/SKALE Chain-containers-versions #### Description Returns SKALE Chain and IMA container versions. #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/SKALE Chain-containers-versions {"data": {"skaled_version": "3.5.12-stable.1", "ima_version": "1.0.0-develop.148"}, "error": null} ``` ### /status/btrfs #### Description Returns btrfs kernel information. #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/btrfs ``` ### /status/ssl #### Description Returns key-cert pair validity. #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/ssl ``` #### Example Response ```json theme={null} {"data": {"issued_to": "skale.network.cloud", "expiration_date": "2021-11-08T17:45:04"}, "error": null} ``` ### /status/ima #### Description Returns the status of the IMA container. ### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/ima ``` #### Example Response ```json theme={null} {"data": [{"skale-chain-name": {"error": "IMA docker container is not running", "last_ima_errors": []}}], "error": null} ``` ### /status/public-ip #### Description #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/public-ip ``` #### Example Response ```json theme={null} {"data": {"public_ip": "1.2.3.4"}, "error": null} ``` ### /status/validator-nodes #### Description #### Usage ```shell theme={null} $ curl http://YOUR_SKALE_NODE_IP:3009/status/validator-nodes ``` #### Example Response ```json theme={null} {"data": [[1, "1.2.3.4", true], [2, "1.2.3.5", false]], "error": null} ``` # SKALE Watchdog Source: https://docs.skale.space/validators/watchdog/overview Overview of SKALE Watchdog SKALE Watchdog is microservice running on every SKALE supernode for providing SLA agents with supernode performance metrics. It also can be used for external monitoring goals (validators can use it in their monitoring tools). ## How It Works Watchdog is a Python script running in docker container with Flask web server that provides simple REST JSON API available on port 3009 (http\://YOUR\_SKALE\_NODE\_IP:3009). Currently, Watchdog can give data on all SKALE supernode docker containers, health checks of SKALE Chains and SGX server, Ethereum node endpoint status, and hardware information. Node CLI automatically opens port 3009 on the machine using iptables. Be sure that port 3009 is open for the supernode’s external network for Watchdog to work.