# RISE Documentation (/docs) import { Rocket, Globe, Wallet, ArrowLeftRight, Layers, Dices, Clock, GitFork } from 'lucide-react'; RISE is an Ethereum Layer 2 blockchain delivering near-instant transactions at unprecedented scale with sub-3ms latency and 100,000+ TPS capacity, all secured by Ethereum. Built for CEX-grade performance and full EVM composability, RISE enables builders, traders, and institutions to create and connect to global orderbooks alongside a thriving DeFi ecosystem with ease. ## Explore } title="Network Details" href="/docs/builders/network-details" description="RPC, Chain ID, and explorer info" /> } title="Builder Quickstart" href="/docs/builders/quick-start" description="Start building on RISE" /> } title="Parallel EVM" href="/docs/rise-evm/pevm" description="The ultimate parallel EVM execution engine" /> } title="RISE Wallet" href="/docs/rise-wallet" description="Gas sponsored, passkey compatible wallet integration" /> } title="RISEx" href="/docs/risex" description="RISE's native decentralized exchange" /> } title="Shreds" href="/docs/builders/shreds" description="RISE's mini-block like structures" /> } title="VRF" href="/docs/builders/vrf" description="Verifiable randomness on RISE" /> ## AI & LLM Access These docs are optimized for AI consumption. You can access the content in machine-readable formats: * **Full documentation**: [/llms-full.txt](/llms-full.txt) - All docs in a single text file * **Individual pages**: Append `.mdx` to any page URL (e.g., `/docs/get-started/rise.mdx`) to get the raw markdown This makes it easy to feed RISE documentation into AI assistants like ChatGPT, Claude, or other LLMs. ## Community Join our growing community to stay updated and engage with the RISE ecosystem: * [Discord](https://discord.gg/risechain) * [Twitter](https://x.com/risechain) * [GitHub](https://github.com/risechain) # Mermaid Test (/docs/test-mermaid) # Mermaid Diagram Test This page demonstrates Mermaid diagram rendering. ## State Diagram Here's your state diagram from the image: ## Flow Chart ## Sequence Diagram ## Gantt Chart # Components (/docs/test) ## Code Block ```js console.log('Hello World'); ``` ## Cards # Contract Addresses (/docs/builders/contract-addresses) This page provides a reference for all contract addresses on RISE Testnet. ## Pre-deployed Contracts These contracts are pre-deployed and available from genesis. | Contract Name | Description | Address | | ---------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Create2Deployer | Helper for CREATE2 opcode usage | [`0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2`](https://explorer.testnet.riselabs.xyz/address/0x13b0D85CcB8bf860b6b79AF3029fCA081AE9beF2) | | DeterministicDeploymentProxy | Integrated with Foundry for deterministic deployments | [`0x4e59b44847b379578588920ca78fbf26c0b4956c`](https://explorer.testnet.riselabs.xyz/address/0x4e59b44847b379578588920ca78fbf26c0b4956c) | | MultiCall3 | Allows bundling multiple transactions | [`0xcA11bde05977b3631167028862bE2a173976CA11`](https://explorer.testnet.riselabs.xyz/address/0xcA11bde05977b3631167028862bE2a173976CA11) | | GnosisSafe (v1.3.0) | Multisignature wallet | [`0x69f4D1788e39c87893C980c06EdF4b7f686e2938`](https://explorer.testnet.riselabs.xyz/address/0x69f4D1788e39c87893C980c06EdF4b7f686e2938) | | GnosisSafeL2 (v1.3.0) | Events-based implementation of GnosisSafe | [`0xfb1bffC9d739B8D520DaF37dF666da4C687191EA`](https://explorer.testnet.riselabs.xyz/address/0xfb1bffC9d739B8D520DaF37dF666da4C687191EA) | | MultiSendCallOnly (v1.3.0) | Batches multiple transactions (calls only) | [`0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B`](https://explorer.testnet.riselabs.xyz/address/0xA1dabEF33b3B82c7814B6D82A79e50F4AC44102B) | | MultiSend (v1.3.0) | Batches multiple transactions | [`0x998739BFdAAdde7C933B942a68053933098f9EDa`](https://explorer.testnet.riselabs.xyz/address/0x998739BFdAAdde7C933B942a68053933098f9EDa) | | Permit2 | Next-generation token approval system | [`0x000000000022D473030F116dDEE9F6B43aC78BA3`](https://explorer.testnet.riselabs.xyz/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) | | EntryPoint (v0.7.0) | ERC-4337 entry point for account abstraction | [`0x0000000071727De22E5E9d8BAf0edAc6f37da032`](https://explorer.testnet.riselabs.xyz/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032) | | SenderCreator (v0.7.0) | Helper for EntryPoint | [`0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C`](https://explorer.testnet.riselabs.xyz/address/0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C) | | WETH | Wrapped ETH | [`0x4200000000000000000000000000000000000006`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000006) | ## L1 (Sepolia) System Contracts These contracts are deployed on the Sepolia Ethereum testnet and handle the communication between L1 and RISE Testnet. | Contract Name | Description | Address | | --------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | AnchorStateRegistryProxy | Stores state roots of the L2 chain | [`0x5ca4bfe196aa3a1ed9f8522f224ec5a7a7277d5a`](https://sepolia.etherscan.io/address/0x5ca4bfe196aa3a1ed9f8522f224ec5a7a7277d5a) | | BatchSubmitter | Submits batches of transactions | [`0x45Bd8Bc15FfC21315F8a1e3cdF67c73b487768e8`](https://sepolia.etherscan.io/address/0x45Bd8Bc15FfC21315F8a1e3cdF67c73b487768e8) | | Challenger | Handles challenges to invalid state transitions | [`0xb49077bAd82968A1119B9e717DBCFb9303E91f0F`](https://sepolia.etherscan.io/address/0xb49077bAd82968A1119B9e717DBCFb9303E91f0F) | | DelayedWETHProxy | Wrapped ETH with withdrawal delay | [`0x3547e7b4af6f0a2d626c72fd7066b939e8489450`](https://sepolia.etherscan.io/address/0x3547e7b4af6f0a2d626c72fd7066b939e8489450) | | DisputeGameFactoryProxy | Creates dispute games for challenging invalid state | [`0x790e18c477bfb49c784ca0aed244648166a5022b`](https://sepolia.etherscan.io/address/0x790e18c477bfb49c784ca0aed244648166a5022b) | | L1CrossDomainMessengerProxy | Handles message passing from L1 to L2 | [`0xcc1c4f905d0199419719f3c3210f43bb990953fc`](https://sepolia.etherscan.io/address/0xcc1c4f905d0199419719f3c3210f43bb990953fc) | | L1ERC721BridgeProxy | Bridge for NFTs between L1 and L2 | [`0xfc197687ac16218bad8589420978f40097c42a44`](https://sepolia.etherscan.io/address/0xfc197687ac16218bad8589420978f40097c42a44) | | L1StandardBridgeProxy | Bridge for ETH and ERC20 tokens | [`0xe9a531a5d7253c9823c74af155d22fe14568b610`](https://sepolia.etherscan.io/address/0xe9a531a5d7253c9823c74af155d22fe14568b610) | | MIPS | MIPS verification for fault proofs | [`0xaa33f21ada0dc6c40a33d94935de11a0b754fec4`](https://sepolia.etherscan.io/address/0xaa33f21ada0dc6c40a33d94935de11a0b754fec4) | | OptimismMintableERC20FactoryProxy | Factory for creating bridged tokens on L2 | [`0xb9b92645886135838abd71a1bbf55e34260dabf6`](https://sepolia.etherscan.io/address/0xb9b92645886135838abd71a1bbf55e34260dabf6) | | OptimismPortalProxy | Main entry point for L1 to L2 transactions | [`0x77cce5cd26c75140c35c38104d0c655c7a786acb`](https://sepolia.etherscan.io/address/0x77cce5cd26c75140c35c38104d0c655c7a786acb) | | PreimageOracle | Stores preimages for fault proofs | [`0xca8f0068cd4894e1c972701ce8da7f934444717d`](https://sepolia.etherscan.io/address/0xca8f0068cd4894e1c972701ce8da7f934444717d) | | Proposer | Proposes new L2 state roots | [`0x407379B3eBd88B4E92F8fF8930D244B592D65c06`](https://sepolia.etherscan.io/address/0x407379B3eBd88B4E92F8fF8930D244B592D65c06) | | SystemConfigProxy | Configuration for the RISE system | [`0x5088a091bd20343787c5afc95aa002d13d9f3535`](https://sepolia.etherscan.io/address/0x5088a091bd20343787c5afc95aa002d13d9f3535) | | UnsafeBlockSigner | Signs blocks in development mode | [`0x8d451372bAdE8723F45BF5134550017F639dFb11`](https://sepolia.etherscan.io/address/0x8d451372bAdE8723F45BF5134550017F639dFb11) | ## L2 (RISE Testnet) System Contracts These are the predeploy contracts on RISE Testnet. | Contract Name | Description | Address | | ----------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | L2ToL1MessagePasser | Initiates withdrawals to L1 | [`0x4200000000000000000000000000000000000016`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000016) | | L2CrossDomainMessenger | Handles message passing from L2 to L1 | [`0x4200000000000000000000000000000000000007`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000007) | | L2StandardBridge | L2 side of the token bridge | [`0x4200000000000000000000000000000000000010`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000010) | | L2ERC721Bridge | L2 side of the NFT bridge | [`0x4200000000000000000000000000000000000014`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000014) | | SequencerFeeVault | Collects sequencer fees | [`0x4200000000000000000000000000000000000011`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000011) | | OptimismMintableERC20Factory | Creates standard bridged tokens | [`0x4200000000000000000000000000000000000012`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000012) | | OptimismMintableERC721Factory | Creates bridged NFTs | [`0x4200000000000000000000000000000000000017`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000017) | | L1Block | Provides L1 block information | [`0x4200000000000000000000000000000000000015`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000015) | | GasPriceOracle | Provides gas price information | [`0x420000000000000000000000000000000000000F`](https://explorer.testnet.riselabs.xyz/address/0x420000000000000000000000000000000000000F) | | ProxyAdmin | Admin for proxy contracts | [`0x4200000000000000000000000000000000000018`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000018) | | BaseFeeVault | Collects base fee | [`0x4200000000000000000000000000000000000019`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000019) | | L1FeeVault | Collects L1 data fees | [`0x420000000000000000000000000000000000001A`](https://explorer.testnet.riselabs.xyz/address/0x420000000000000000000000000000000000001A) | | GovernanceToken | RISE governance token | [`0x4200000000000000000000000000000000000042`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000042) | | SchemaRegistry | EAS schema registry | [`0x4200000000000000000000000000000000000020`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000020) | | EAS | Ethereum Attestation Service | [`0x4200000000000000000000000000000000000021`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000021) | ## Usage Examples ### Bridging ETH from L1 to L2 ```solidity // On Sepolia (L1) IL1StandardBridge bridge = IL1StandardBridge(0xe9a531a5d7253c9823c74af155d22fe14568b610); // Deposit ETH to L2 bridge.depositETH{value: amount}( minGasLimit, emptyBytes // No additional data ); ``` ### Sending a Message from L2 to L1 ```solidity // On RISE Testnet (L2) IL2CrossDomainMessenger messenger = IL2CrossDomainMessenger(0x4200000000000000000000000000000000000007); // Send message to L1 messenger.sendMessage( targetL1Address, abi.encodeWithSignature("someFunction(uint256)", value), minGasLimit ); ``` # Build on RISE (/docs/builders) import { Card, Cards } from 'fumadocs-ui/components/card'; import { Zap, Globe, Wallet, Layers, Dices } from 'lucide-react'; ## Why Build on RISE? RISE is built for builders who demand speed, precision, and zero compromise. Offering the fastest execution and lowest latency available today, RISE empowers developers to create high-performance applications—from CLOBs and actively managed strategies to DePIN and fully onchain gaming. If your users can't wait, build on RISE. * **Blazing Performance**: Over 100,000 transactions per second unlocks massive scalability for next-gen dApps * **Instant Confirmations**: 10ms end-to-end confirmations ensure your applications respond in realtime, even under heavy load * **Ultra-Low Fees**: Optimized for minimal gas costs, making high-frequency and high-volume activity sustainable * **Seamless Development**: Complete EVM compatibility means you can build fast, deploy instantly, and scale without friction ## Getting Started } title="Quick Start" href="/docs/builders/quick-start" description="Deploy your first contract" /> } title="Network Details" href="/docs/builders/network-details" description="Connect to RISE and configure your dev environment" /> ### Tools } title="RISE Wallet" href="/docs/rise-wallet" description="Gasless transactions with passkey authentication" /> } title="Shreds" href="/docs/builders/shreds" description="Realtime 3ms transaction confirmations" /> } title="Fast VRF" href="/docs/builders/vrf" description="Verifiable randomness for gaming and more" /> ## Connect with Builders Join the RISE Developer Community. Connect with other builders, share ideas, get technical support, and collaborate on projects. } title="Builder Discord" href="https://discord.com/invite/qhKnePXdSM" description="Connect with other builders and get support" /> # Internal Oracles (/docs/builders/internal-oracles) Internal price oracles deployed on RISE Testnet. ## Oracle Addresses | Ticker | Address | | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ETH | [`0x7114E2537851e727678DE5a96C8eE5d0Ca14f03D`](https://explorer.testnet.riselabs.xyz/address/0x7114E2537851e727678DE5a96C8eE5d0Ca14f03D) | | USDC | [`0x50524C5bDa18aE25C600a8b81449B9CeAeB50471`](https://explorer.testnet.riselabs.xyz/address/0x50524C5bDa18aE25C600a8b81449B9CeAeB50471) | | USDT | [`0x9190159b1bb78482Dca6EBaDf03ab744de0c0197`](https://explorer.testnet.riselabs.xyz/address/0x9190159b1bb78482Dca6EBaDf03ab744de0c0197) | | BTC | [`0xadDAEd879D549E5DBfaf3e35470C20D8C50fDed0`](https://explorer.testnet.riselabs.xyz/address/0xadDAEd879D549E5DBfaf3e35470C20D8C50fDed0) | ## Usage The oracle price for each asset can be fetched by calling the `latest_answer` function on the respective oracle address. ```solidity interface IPriceOracle { function latest_answer() external view returns (int256); } // Example: Get ETH price IPriceOracle ethOracle = IPriceOracle(0x7114E2537851e727678DE5a96C8eE5d0Ca14f03D); int256 ethPrice = ethOracle.latest_answer(); ``` # Network Details (/docs/builders/network-details) Use the information below to connect and submit transactions to RISE. | Property | Testnet | | --------------- | --------------------------------------- | | Network Name | RISE Testnet | | Chain ID | `11155931` | | RPC URL | `https://testnet.riselabs.xyz` | | Explorer | `https://explorer.testnet.riselabs.xyz` | | Currency Symbol | ETH | ## Using with Development Tools ### Hardhat Configuration ```javascript // hardhat.config.js module.exports = { networks: { riseTestnet: { url: "https://testnet.riselabs.xyz", chainId: 11155931, accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [] } } }; ``` ### Foundry Configuration ```toml # foundry.toml [profile.default] src = "src" out = "out" libs = ["lib"] [rpc_endpoints] rise_testnet = "https://testnet.riselabs.xyz" [blockscout] rise_testnet = { key = "", url = "https://explorer.testnet.riselabs.xyz/api" } ``` ## Gas & Transaction Details | Property | Value | | ----------------------------- | ------------------ | | Max Gas Limit per Transaction | 16M | | Nonce Order | Enforced on-chain | | Data Storage Fees | Same as L1 | | Blocks to Finality | 259,200 (\~3 days) | ## Getting Testnet ETH } title="RISE Faucet" href="https://faucet.testnet.riselabs.xyz" description="Get free testnet ETH and other tokens for testing" /> # Quick Start (/docs/builders/quick-start) RISE is fully EVM compatible. You can use your existing Ethereum development tools and workflows with minimal configuration changes. ## Smart Contracts Learn how to create a new smart contract project, compile your contracts, and deploy them to RISE. } title="Remix IDE" href="/docs/builders/smart-contracts/remix" description="Deploy contracts on RISE using Remix IDE" /> } title="Hardhat" href="/docs/builders/smart-contracts/hardhat" description="Set up Hardhat for smart contract development on RISE" /> } title="Foundry" href="/docs/builders/smart-contracts/foundry" description="Set up Foundry for smart contract development on RISE" /> ## Applications Learn how to build frontend applications to interact with smart contracts on RISE. } title="Viem" href="/docs/builders/frontend/viem" description="Build apps using Viem" /> } title="Ethers.js" href="/docs/builders/frontend/ethers" description="Build apps using Ethers.js" /> # RISE Wallet Stack (/docs/builders/rise-wallet) import { Card, Cards } from 'fumadocs-ui/components/card'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Zap, Key, Code, BookOpen } from 'lucide-react'; RISE Wallet is a chain-native wallet layer that provides gasless transactions, passkey authentication, and session keys for your dApp. Built on audited smart accounts from Porto and integrated directly into RISE's infrastructure. ## Key Features * **Gasless by Default**: Users don't need ETH to start using your app * **Passkey Authentication**: No seed phrases - just biometrics (FaceID, TouchID) * **Session Keys**: Enable high-frequency interactions without popups * **3ms Confirmations**: Leverages RISE's shred architecture for instant feedback ## Quick Integration ```bash npm i rise-wallet wagmi viem ``` ```bash pnpm add rise-wallet wagmi viem ``` ```bash yarn add rise-wallet wagmi viem ``` ```bash bun add rise-wallet wagmi viem ``` ```tsx import { Chains, RiseWallet } from "rise-wallet"; import { riseWallet } from "rise-wallet/wagmi"; export const rwConnector = riseWallet(RiseWallet.defaultConfig); ``` ## Learn More } title="Overview" href="/docs/rise-wallet" description="Introduction to RISE Wallet features and benefits" /> } title="Getting Started" href="/docs/rise-wallet/minting" description="Interactive tutorials for common use cases" /> } title="Session Keys" href="/docs/rise-wallet/session-keys" description="Build popup-free experiences" /> } title="Integration Guides" href="/docs/rise-wallet/wagmi" description="Framework-specific documentation" /> # Testnet Tokens (/docs/builders/testnet-tokens) ERC20 tokens deployed on RISE Testnet (Chain ID: 11155931). These tokens have been minted purely for testing purposes. They hold no value. You can acquire tokens via the faucet on the [Testnet Portal](https://portal.risechain.com). ## Token Addresses | Name | Symbol | Decimals | Address | | --------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Wrapped ETH | WETH | 18 | [`0x4200000000000000000000000000000000000006`](https://explorer.testnet.riselabs.xyz/address/0x4200000000000000000000000000000000000006) | | USD Coin | USDC | 6 | [`0x8a93d247134d91e0de6f96547cb0204e5be8e5d8`](https://explorer.testnet.riselabs.xyz/address/0x8a93d247134d91e0de6f96547cb0204e5be8e5d8) | | Tether USD | USDT | 8 | [`0x40918ba7f132e0acba2ce4de4c4baf9bd2d7d849`](https://explorer.testnet.riselabs.xyz/address/0x40918ba7f132e0acba2ce4de4c4baf9bd2d7d849) | | Wrapped Bitcoin | WBTC | 18 | [`0xf32d39ff9f6aa7a7a64d7a4f00a54826ef791a55`](https://explorer.testnet.riselabs.xyz/address/0xf32d39ff9f6aa7a7a64d7a4f00a54826ef791a55) | | RISE | RISE | 18 | [`0xd6e1afe5ca8d00a2efc01b89997abe2de47fdfaf`](https://explorer.testnet.riselabs.xyz/address/0xd6e1afe5ca8d00a2efc01b89997abe2de47fdfaf) | | Mog Coin | MOG | 18 | [`0x99dbe4aea58e518c50a1c04ae9b48c9f6354612f`](https://explorer.testnet.riselabs.xyz/address/0x99dbe4aea58e518c50a1c04ae9b48c9f6354612f) | | Pepe | PEPE | 18 | [`0x6f6f570f45833e249e27022648a26f4076f48f78`](https://explorer.testnet.riselabs.xyz/address/0x6f6f570f45833e249e27022648a26f4076f48f78) | ## Contract Features All tokens implement: * ERC20 standard functionality (transfer, approve, transferFrom) * Custom decimals support (6 to 18) * Minting capability (restricted to owner) * Burning capability (anyone can burn their own tokens) ### WETH The WETH token at `0x4200000000000000000000000000000000000006` is a predeploy contract with additional functionality: * Wrap ETH by sending ETH to the contract or calling `deposit()` * Unwrap WETH by calling `withdraw(uint)` * Standard ERC20 interface for wrapped ETH ## Example Commands ```bash # Check token balance cast call "balanceOf(address)(uint256)" --rpc-url https://testnet.riselabs.xyz # Transfer tokens cast send "transfer(address,uint256)(bool)" --private-key $PRIVATE_KEY --rpc-url https://testnet.riselabs.xyz # Wrap ETH cast send 0x4200000000000000000000000000000000000006 "deposit()" --value --private-key $PRIVATE_KEY --rpc-url https://testnet.riselabs.xyz # Unwrap WETH cast send 0x4200000000000000000000000000000000000006 "withdraw(uint256)" --private-key $PRIVATE_KEY --rpc-url https://testnet.riselabs.xyz ``` # Based Sequencing (/docs/rise-evm/based-sequencing) # Based Sequencing ## The Sequencer Problem At the core of every rollup is the sequencer, a critical entity that manages transaction flow on the rollup, ensuring efficient and secure interaction with L1.
Centralized Sequencers Centralized Sequencers
The sequencer fetches and orders incoming transactions from the mempool into blocks (similar to block proposers in regular blockchains). It also creates L2 checkpoints by posting the latest state commitment to L1. It acts as an intermediary between the L2 and L1, performing the following functions. * **Ordering.** The sequencer receives transactions from users on the L2, orders them, and includes them in blocks. * **Execution.** The sequencer executes the transactions and updates the state of the rollup. * **Batching.** The sequencer groups transactions into batches, compresses the data, and submits these batches to Ethereum. * **Preconfirmation**. The sequencer advertises an RPC endpoint for users to submit transactions to. As a response, the sequencer issues soft confirmations on user transactions. ### Centralization Risks Traditional rollups favor centralized sequencers because they offer high performance on dedicated hardware, low latency for better user experience. Low latency is crucial for applications that require quick transaction confirmations, such as perpetual exchanges or lending platforms. Last but not least, a rollup with centralized sequencers is simple to implement, mainly because no consensus is required. However, a centralized sequencer becomes a **single point of failure**, making the entire rollup vulnerable to technical failures, attacks, or manipulations. A centralized sequencer has the absolute power to censor transactions, reorder transactions to favor their own interests, go offline and halt the rollup entirely, or extract bad MEV from users. This raises concerns about censorship resistance. If they refuse to process certain types of transactions, perhaps due to regulatory pressure or self-interest, users may find themselves unable to execute essential operations within the ecosystem. The impact of censorship undermines the fundamental principles of a permissionless network. *** ## Based Sequencing: Leveraging Ethereum's Security [Based sequencing](https://ethresear.ch/t/based-rollups-superpowers-from-l1-sequencing/15016) removes the centralized sequencer entirely. Instead, L1 block proposers, the same entities responsible for building Ethereum blocks, serve as sequencers for the rollup. ### The Philosophy Rather than building a separate L2 consensus mechanism, RISE leverages Ethereum's existing, battle-tested block building pipeline. RISE inherits Ethereum proven liveness and censorship resistance properties backed by around 1M+ validators with no additional consensus layer. ### How It Works
General paradigm of based sequencing General paradigm of based sequencing
Users submit transactions to the current L1 proposer. The L1 proposer orders these transactions alongside regular L1 transactions. The rollup's execution node processes these transactions off-chain and generates a new state commitment. ## Based Preconfirmations While based sequencing solves centralization, it introduces a new problem: **latency**. Users must wait for L1 blocks to be included (typically 12 seconds) before seeing transaction confirmation, even though the sequencer is already part of L1. This is not wanted in a rollup as users are familiar with fast confirmations given by centralized sequencers. RISE solves this with **based preconfirmations**: cryptographically enforced promises from Ethereum proposers that certain events will happen in upcoming blocks. ### What Are Preconfirmations? A **preconfirmation** (or **preconf**) is a collateral-backed promise by the current proposer (or a **gateway**, whomever the proposer delegates to) that certain events will happen at a specific future timestamp. The fundamental shift is moving from **trust-based systems** to **a more trustless and cryptographically enforced system**. Instead of trusting a centralized sequencer, RISE uses Ethereum-enforced slashing mechanisms, and collateral requirements to reduce transaction latency from 12 seconds to milliseconds while maintaining security guarantees. ### Gateway Issuing preconfs adds many requirements to L1 proposers. To mitigate this sophistication, in our design, we assume the proposer delegates sequencing right to a sophisticated entity called a gateway (*a.k.a* a **preconfer**). Gateways function as intermediaries connecting Ethereum's block-building market to the L2. They serve as sequencers while providing L1-secured preconfs to users. Unlike traditional sequencers, gateways face slashing penalties for not honoring preconfs. Gateway The delegation is somewhat similar to how most proposers today delegate their block building tasks to builders. The delegation of preconf rights can occur through on-chain or off-chain mechanisms. We consider the off-chain approach (i.e, via the existing PBS pipeline) in this design. #### Registration and Collateral A registry contract is deployed for any L1 validators to sign up to become a sequencer for the RISE rollup. Upon registration, L1 validators also need to stake some collateral. This collateral requirement ensures that L1 validators are economically discouraged to misbehave as this will result in their collateral being slashed. #### Slashing The system penalizes two categories of violations: **liveness faults** (when preconfs were not included and the proposer's slot was missed) and **safety faults** (when preconfs were not included despite the proposer's slot being available). Both trigger slashing events that penalize the misbehaving validator's collateral. #### Gateway Delegation and PBS Integration Proposers delegate sequencing rights to gateways through existing PBS (Proposer-Builder Separation) pipelines. This architecture mirrors builder relationships in current Ethereum MEV workflows, ensuring compatibility with the existing block-building ecosystem. #### Batch Processing with Shreds Preconfs are issued in batches using [shreds](/docs/rise-evm/shreds). Batch preconfs inherently align with the operational efficiency of rollups, which are designed to process transactions in bulk. By issuing a single preconf for multiple transactions, rollups can potentially achieve lower overhead per transaction compared to providing individual preconf for each user. Shreds are currently variable in time to balance between throughput and latency. ### Preconf Propagation When users submit transactions, they send them to any node, which forwards them to the current gateway. The gateway orders these transactions into a shred, executes the shred, and broadcasts the resulting shred through the P2P network. This design ensures that preconfs are visible across the network in near realtime, giving users and applications strong assurance that their transactions will be included/executed. More details about shred propagation can be found [here](/docs/rise-evm/shreds#block-propagation). ### Fallback Gateway Mechanism To maintain liveness and availability, RISE employs a fallback gateway mechanism. This ensures the network continues functioning during gateway unavailability and addresses two critical scenarios: * **Cold-start problem**. In the early state of Phase 3 (see below), there might be only a few L1 validators opted into sequencing, creating gaps between preconf slots. A fallback gateway ensures continuous sequencing availability during this transition period. * **Liveness failures**. When an active gateway experiences operational issues that prevent rollup progression, a fallback sequencer steps in immediately rather than waiting for the full sequencing window to expire. The system monitors the maximum duration between consecutive state commitments. If no L2 state commitment is published within this period, the next gateway in rotation can take over immediately. This approach balances safety during early phases with the decentralization goals of the long-term vision, ensuring users always have confirmed transactions while progressively removing central dependencies. *** ## Implementation Roadmap RISE implements based sequencing in three phases, progressively decentralizing the network while maintaining operational maturity throughout the transition. ### Phase 1: The Taste The initial phase extends the current RISE sequencer to incorporate gateway functionalities. This phase proves that the gateway architecture works in practice and validates all necessary components before broader adoption. Key activities include implementing L1 interactions (slashing and delegation mechanisms), issuing L1-secured execution preconfs as shreds, testing gateway component maturity and reliability, and validating software stability under production conditions. By the end of this phase, a single gateway provides fast, L1-secured preconfs to users, slashing and delegation mechanisms are battle-tested, the system proves capable of handling transaction volume and latency requirements, and infrastructure is ready for decentralized participation. ### Phase 2: The Aligning This phase serves as a transitional phase before full permissionlessness. Multiple whitelisted gateways operate in a **round-robin rotation system**. RISE will remain in this phase long enough to ensure everything works correctly, the system is robust, and operational processes are mature before moving to full permissionlessness. During this phase, a default gateway (operated by RISE) serves as a fallback gateway to ensure the rollup's liveness. #### How Rotating Works Multiple gateways take turns to sequence the rollup. Each gateway operates during a fixed sequencing window (measured in L1 blocks) before handing off to the next gateway. Gateways rotate sequencing duties with each receiving equal opportunity to sequence during its window. The selection mechanism is fully deterministic and transparent on-chain: anyone can identify the current and next gateway using a deterministic formula derived from the L1 Registry contract. #### Efficient Handover The system ensures smooth handover through two mechanisms. * **Shred streaming** provides realtime block segments that deliver near-instantaneous state updates between gateways. * **Mempool synchronization** maintains a direct communication channel between current and the next gateways so they have consistent transaction pool views, allowing the next gateway to pre-process transactions not yet processed by the current gateway for instantaneous handover. #### Enhanced Liveness and Censorship Resistance This phase enhances the rollup's robustness in several ways. Multiple whitelisted but independent gateways share sequencing duties for decentralization, backup gateways prevent downtime if the current gateway goes offline, no single entity controls transaction ordering for censorship resistance, and the system survives individual gateway failures through redundancy. Rotation prevents any single entity from dominating block building over time, building long-term censorship capabilities. ### Phase 3: The Basedening The final phase removes gateway whitelisting entirely for full permissionless participation. Any Ethereum validator can become a gateway by delegating block-building rights through collateral staking. Gateways compete freely for users and transaction ordering, economic incentives align as validators earn fee portions for including rollup transactions. The economic incentives are straightforward: when proposers propose blocks, they include rollup transactions and receive fee revenue as compensation for sequencing. By this phase, rollup decentralization grows with Ethereum's validator set, no single entity can have full control over gateway participation, users benefit from free market competition among sequencers, and full permissionless composability with Ethereum is achieved. ## The Ultimate Vision When fully implemented, the boundary between RISE and Ethereum becomes smaller. RISE becomes not just a fast L2, but a natural extension of Ethereum itself, offering security with L1-enforced slashing, L1-secured near realtime preconf rather than waiting for L1 confirmation, full composability with Ethereum's block-building market. # Continuous Block Pipeline (/docs/rise-evm/cbp) # Continuous Block Pipeline (CBP) ## The Problem with Traditional Block Production In typical Layer 2 systems, block production follows a strictly sequential process, where only a small portion of the total blocktime is spent on actual transaction execution. This inefficiency stems from the traditional flow. * **Consensus (C)**: Deriving L1 transactions and new block attributes. This phase blocks all other operations. * **Execution (E)**: Executing derived transactions as well as L2 transactions from mempool. Execution is CPU-intensive. * **Merkleization (M)**: Sealing the block by computing the new commitment to the state. Merkleization is IO-intensive and increases in cost as state size grows.
Block Pipeline OP Stack Block Pipeline OP Stack
*A typical block pipeline for an L2 with a one-second block time and large state. The block gasLimit will be limited by how much gas can be processed in the time allocated to execution; however, in this system, execution accounts for only a minority of the block-building pipeline. The first block in an epoch has longer consensus time due to L1 and deposit derivation.* During the block-building pipeline, most of the blocktime is allocated to consensus and merkleization, leaving a tiny portion of total blocktime for execution. This insufficient use of blocktime leads to poor performance on many L2s. ## Execution-Merkleization Separation To speed up execution time, many Ethereum clients separate their databases for execution and merkleization. During execution, block executors use a flat database (flatDB) to read states. The flatDB provides O(1) time complexity for reading and writing key-value pairs, much faster than the MPT's log-squared complexity, enabling fast block executions. All changes to keys are recorded in a **ChangeSet**. After block execution completes, the merkleization process updates the MPT with all changes from the ChangeSet to generate the full state commitment. ## RISE's Continuous Block Pipeline Since execution and merkleization can operate on different databases, the next block's execution can start as soon as the current block's execution finishes without waiting for merkleization. CBP restructures the block building pipeline to reduce execution idle time.
Continuous Block Pipeline Continuous Block Pipeline
The idea is simple: we perform execution if there are transactions residing in the mempool. We consider two cases: ### First L2 block in an epoch For this block, consensus must derive new L1 block information and include L1→L2 deposited transactions. Therefore, execution of this block must be performed after consensus, since deposited transactions are prioritized and might invalidate L2 transactions. ### Other blocks in an epoch L1 information within an epoch is the same for all blocks, therefore consensus for these blocks mostly depends on the previous block. Since there are no L1-derived transactions in these blocks, execution can safely start as soon as the execution of the previous block finishes. ## Benefits This approach offers several key benefits: * **Continuous Execution of Transactions**. The execution thread monitors the mempool for transactions and executes them in multiple block segments, no longer waiting for consensus to request a new block * **Higher Execution Throughput**. Execution of the next block happens simultaneously with merkleization of the current block, allocating more time for execution * **Optimized Mempool Processing**. A new mempool structure balances latency and throughput by pre-ordering transactions to minimize shared states and maximize parallel execution CBP enables RISE to achieve exceptional throughput while maintaining the safety and consistency guarantees expected from modern rollups. # Data Availability (/docs/rise-evm/data-availability) # Data Availability ## Motivation Data availability (DA) guarantees that all the necessary information to reconstruct the state of a rollup is available to anyone who needs it. This is crucial for the security of rollups, as it allows anyone to independently verify as well as challenge the validity of transactions and the state of the rollup. Furthermore, DA ensures that users can still access their funds and withdraw from the rollup even if the rollup itself (i.e, the sequencer) goes offline. Ethereum introduced a new DA layer (blobs) to complement to the old `calldata` DA in its [Dencun hardfork](https://ethereum.org/en/roadmap/dencun/) with EIP-4844. Blobs enable cheaper DA costs (compared to `calldata`) as blobs’ data is transient and will be expired in around 18 days. This in turn helps reduce the overall cost for a rollup. However, the current blob throughput is limited. At the current state, Ethereum targets 6 blobs per block, with the maximum of 9 blobs. This translates to the target throughput of 64KB/s. The blob throughput is expected to (theoretically) be 8x after the Fukasa upgrade (expected late 2025), this will scale the target throughput to 512KB/s through PeerDAS v1.x. However, even with the upgrade, this throughput is insufficient for a high-load rollup like RISE. *** ## EigenDA We use [EigenDA](https://www.eigenda.xyz/) as the main DA layer for our rollup in the normal case. EigenDA’s Mainnet currently supports a throughput of [100 MB/s](https://x.com/0xkydo/status/1950571973790363737), with the average confirmation of 5s. With this impressive throughput, EigenDA is able to provide sufficient throughput for RISE to operate upon. EigenDA also offers good Ethereum alignment as it leverages restaking through EigenLayer to achieve native Ethereum integration through EigenLayer, directly leveraging Ethereum's validator set via restaking. EigenDA uses Ethereum for operator registration, dispute resolution, and settlement, with no separate consensus layer. *** ## Ethereum Blob Fallback In the event of EigenDA unavailability, the rollup can fall back to posting transactions to Ethereum’s blobs. This helps maintain the rollup’s liveness and ensure users’ funds not getting stuck in the rollup’s bridge. Ethereum fallback is triggered whenever the `op-batcher` receives an error from EigenDA or fails to receive any acknowledgement from EigenDA, or in the case where the batcher does not have enough funds to pay EigenDA transaction fees. After issues with EigenDA have been addressed, the `op-batcher` will switch back to EigenDA as the main DA layer. # RISE EVM Architecture (/docs/rise-evm) # Architecture Traditional web servers update state in a continuous, interrupt-driven manner. This means transactions are processed as soon as they arrive. When demand is high, servers implement queueing to manage the load and process requests as resources become available. This is the exact UX we aim to achieve with RISE. RISE provides developers with exceptional performance and capabilities while delivering near-instant latency and seamless user experience. RISE achieves over 1 GGas/s with millisecond-latency for immediate transactions. ## Components The figure below details the high-level architecture of the RISE stack.
RISE Stack Architecture RISE Stack Architecture
* **Execution**. A revolutionary EVM-compatible execution engine that redefines performance with infinite speed. * **Parallel EVM (pevm)**. The ultimate parallel EVM engine * **Continuous Block Pipeline (CBP)**. Executing transactions while residing in mempool. * **Shreds**. Efficient interrupt-driven block construction. * **RiseDB**. A custom DB specifically designed for EVM chain states. * **Data Availability**. A highly performant DA layer with Ethereum fallbacks. * **ZK Fraud Proofs**. Simpler fraud proofs for better UX. * **Based Sequencing**. The RISE's plan for decentralization. ## Core Components The RISE stack delivers exceptional performance through these key components: ### Execution Engine * **[Continuous Block Pipeline](/docs/rise-evm/cbp)** - Continuous block processing pipeline for optimal throughput * **[Shreds](/docs/rise-evm/shreds)** - Fast transaction shredding for parallel execution ### Settlement & DA * **[ZK Fraud Proofs](/docs/rise-evm/zk-fraud-proofs)** - Hybrid rollup approach combining optimistic and ZK proving for efficient fraud resolution * **[Data Availability](/docs/rise-evm/data-availability)** - Modular DA layer leveraging Celestia and EigenDA for scalability ### Network Layer * **[Transaction Lifecycle](/docs/rise-evm/tx-lifecycle)** - Complete transaction processing pipeline from submission to finality * **[Network Participants](/docs/rise-evm/network-participants)** - Overview of RISE network participant types, roles, and hardware requirements ### Future Improvements * **[Parallel EVM (PEVM)](/docs/rise-evm/pevm)** - The ultimate parallel EVM engine * **[Based Sequencing](/docs/rise-evm/based-sequencing)** - Decentralized sequencing leveraging Ethereum L1 with cryptographically enforced preconfirmations and rotating gateways These components work together to deliver over 100,000 TPS with sub-10ms latency while maintaining full EVM compatibility and Ethereum's security guarantees. # Layer 2 Architecture (/docs/rise-evm/layer-2) Content coming soon. # Network Participants (/docs/rise-evm/network-participants) # Network Participants Traditional blockchains requiring every node to re-execute all transactions face significant drawbacks that hinder scalability, decentralization, and efficiency. This creates a problem when a blockchain wants to scale up its performance: **it needs to scale up the specs for all nodes in the network**. This is not wanted as fewer participants are able to join the network. RISE's architecture features specialized network participants with distinct roles and hardware requirements, designed to enable scalability without requiring all nodes to re-execute transactions. Together with different optimization strategies, nodes are aided by the sequencer when performing state updates, further lowering hardware requirements to minimums.
Network Architecture Network Architecture
In this doc, we use Capitalized words to denote distinct types of network participants. This convention helps clearly differentiate key roles such as **Sequencers**, **Replicas**, **Fullnodes**, **Challengers**, and **Provers** emphasizing their specific responsibilities within the network. *** ## Participants ### Sequencers Sequencers serve as the network's core, ordering and processing transactions, then batching them for L1 submission. They leverage optimized execution engines (including [pevm](/docs/rise-evm/pevm), [CBP](/docs/rise-evm/cbp) and [shreds](/docs/rise-evm/shreds)) to achieve high performance. In RISE's based sequencing model, sequencers are [gateways](/docs/rise-evm/based-sequencing) operated by Ethereum validators. ### Replicas Replicas synchronize with the chain by applying state-diffs from the Sequencer rather than re-executing transactions. This approach allows indexing services, block explorers, and archival nodes to operate on commodity hardware while relying on fraud proofs for security verification. Replicas maintain the full chain state without the computational overhead of re-execution. ### Fullnodes Fullnodes re-execute transactions to independently verify state updates, requiring higher hardware specs than Replicas but lower than Sequencers. They benefit from metadata (e.g, state-diff, dependency DAG) provided during synchronization, allowing them to verify state changes more efficiently than re-execution. Fullnodes provide a security checkpoint for the network because they can detect and challenge invalid state transitions. ### Challengers As an L2, we also need a special party to submit challenges to the L1 when it detects a misbehavior of the Sequencer. This party is called **Challenger**. A Challenger must maintain a Fullnode to be able to re-execute transactions provided by the Sequencer. It only requires a single honest Challenger to maintain the security of the L2 chain. This economic incentive model ensures that even if most participants are dishonest, one honest Challenger can protect all users. ### Provers Provers generate validity proofs using specialized hardware accelerators (FPGA/GPU) when fraud challenges occur. They operate only when needed, activated only when the Sequencer misbehaves, making them cost-effective participants. Provers don't need to run continuously; they can be brought online on-demand as disputes arise. *** ## Hardware Specs Sequencers consume the most resources because they must execute all transactions with high performance to maintain network throughput. Replicas are optimized for accessibility, requiring minimal hardware since they avoid transaction re-execution. Fullnodes and Challengers sit in the middle, requiring enough resources for independent verification. Provers operate on-demand with specialized accelerators, making them cost-effective despite their hardware needs. | Node Type | Sync Method | Security | Hardware Requirements | | --------------- | ---------------------- | ------------------------------ | ------------------------------------------ | | **Sequencers** | Self-execution | High | 32GB RAM | | **Replica** | State-diff appliance | Low, depending on fraud proofs | 8-16GB RAM | | **Fullnodes** | Re-execution with aids | High, same as the Sequencer | 16-32GB RAM | | **Challengers** | Same as Fullnodes | High, same as Fullnodes | 16-32GB RAM | | **Provers** | Trusting the Sequencer | N/A | Depending on proving services, rarely used | The diversity in hardware requirements ensures RISE can accommodate participants ranging from individual operators running lightweight Replicas to infrastructure providers operating high-performance Sequencers, all contributing to the security and resilience of the network. # Parallel EVM (PEVM) (/docs/rise-evm/pevm) # Parallel EVM (PEVM) Note the PEVM is not currently live on RISE & is a future improvement. We have already published the PEVM implementation in [GitHub](https://github.com/risechain/pevm) for reference. With our current architecture still able to achieve 1 GGas/s with sub 3 millisecond-execution. ## Motivation ### The Sequential Execution Bottleneck Ethereum's execution model processes transactions sequentially by design. This sequential constraint stems from a fundamental requirement for distributed consensus: **all network participants must compute identical results**. While this sequential guarantee ensures correctness, it creates a severe performance limitation. Modern CPUs contain 8 to 16 cores, yet the EVM execution path utilizes only a single core. The remaining cores are idle while waiting for the current transaction to complete. Additionally, independent transactions that could be executed in parallel are still executed in a sequential manner, creating unnecessary latency for users and limiting the network throughput. As a result, there exists a performance gap between Ethereum (and its rollups) and competitors like Solana: * Ethereum and its rollups combined are processing around only [200-300 TPS](https://rollup.wtf/) across 50+ rollups. * In contrast, Solana consistently produces 1000-2000 TPS, about 10 times larger than that of all rollups combined. ### EVM Parallelization Challenges Parallel execution for blockchains has gained prominence with Aptos, Sui, and Solana, but EVM-compatible implementations face unique challenges. Early attempts to parallelize EVM execution, notably by Polygon and Sei using [Block-STM](https://arxiv.org/abs/2203.06871), showed limited gains, mainly due to: 1. Lack of EVM-specific optimizations tailored to Ethereum's state access patterns. 2. Implementation limitations in languages like Go (with garbage collection pauses). 3. Overhead from synchronization mechanisms that negate parallelism benefits. ### Slow State Root Calculation Beyond transaction execution, the full block processing pipeline faces a secondary bottleneck. After executing all transactions, nodes must calculate the Merkle root of the new state (the state root). This computation can be as expensive or even more expensive than transaction execution itself. If state root calculation exceeds the block time, there will be less time left for execution, causing a performance loss. [RISE parallel EVM (pevm)](https://github.com/risechain/pevm) sets to address these limitations through a ground-up redesign focused on the EVM's unique characteristics that efficiently **executes transactions, broadcasts results, and calculates the state root in parallel**; tightly implemented in Rust for minimal runtime overheads. *** ## Technical Overview ### What is pevm? pevm is a revolutionary execution engine that enables concurrent processing of EVM transactions while maintaining deterministic outcomes. By distributing transaction execution across multiple CPU cores, pevm dramatically increases throughput and reduces latency compared to traditional sequential execution. Key features include: * Optimistic execution of transactions in parallel. * Detection of transaction dependencies and conflicts to ensure deterministic outcomes. * Compatibility with existing sequential executors for easy integration and performance boosts. ### Optimistic Concurrent Execution pevm is built upon the foundation of [Block-STM's](https://arxiv.org/abs/2203.06871) optimistic concurrent execution: rather than predicting dependencies, pevm assumes transactions are independent, executes them in parallel, then validates the execution afterward. The engine dynamically identifies transaction parallelism without requiring developers to change anything (like explicitly declaring access states in Solana and Sui). Regardless, dApps do need to innovate new parallel designs to get more out of the parallel engine, like [Sharded AMM](https://arxiv.org/abs/2406.05568) and RISE's upcoming novel CLOB; just like how multiprocessors gave rise to the design of multithreaded programs. Overall, this strategy trades computational work (re-executing conflicting transactions) for parallelism. On blocks with numerous independent transactions, re-execution overhead is minimal because few conflicts occur. On blocks with sequential dependencies (e.g., multiple transactions sequentially updating the same contract), re-execution occurs but the system gracefully degrades to near-sequential performance, avoiding the overhead of parallel coordination. ### EVM-Specific Innovations pevm's contribution is not just the Block-STM itself, but rather its adaptation to EVM's specific challenges. #### Lazy Updates All EVM transactions in the same block read and write to the same beneficiary account for gas payments, making all transactions interdependent by default. pevm addresses this by utilizing lazy updates for this account. We mock the balance on gas payment reads to avoid registering a state dependency and only evaluate it at the end of the block or when there is an explicit read. We apply the same technique to other common scenarios such as raw ETH or ERC20 transfers. This enables the ability to parallelize transfers from and to the same address, with only a minor post-processing latency for lazy evaluations. #### Mempool Preprocessing Unlike previous rollups that ordered transactions by first-come-first-served or gas auctions, RISE innovates a new mempool structure that balances latency and throughput. The goal is to pre-order transactions to z shared states and maximise parallel execution. This has a relatively similar effect as the local fee market on Solana, where congested contracts & states are more expensive regarding gas & latency. Since the number of threads to execute transactions is much smaller than our intended TPS, we can still arrange dedicated threads to execute high-traffic contract interactions sequentially and others in parallel in other threads. *** ## Architecture Design Blockchain execution must be deterministic so that network participants agree on blocks and state transitions. Therefore, parallel execution must arrive at the same outcome as sequential execution. Having race conditions that affect execution results would break consensus. ### Legacy Architecture RISE pevm started out with Block-STM's optimistic execution, with a collaborative scheduler and a multi-version data structure to detect state conflicts and re-execute transactions accordingly. pevm comprises several interacting layers: * **Scheduler** manages and distributes tasks to worker threads. It maintains atomic counters for execution and validation task indices, allowing worker threads to claim tasks without conflict. Besides, the scheduler tracks transaction status (ready for execution, currently executing, awaiting validation, validated, or aborting) and manages transaction incarnation numbers (re-execution counts). * **Worker Threads** are executor agents. In pevm, multiple worker threads operate in parallel and independently. Each worker thread executes a sequence of tasks assigned by the scheduler: execution tasks and validation tasks. Workers do not directly synchronize with each other; all coordination occurs through the scheduler and multi-version memory structures. * **Multi-Version Memory (MvMemory)** is the central data for conflict detection. MvMemory preserves a complete history of all writes, indexed by transaction index. For each location, MvMemory tracks which transaction wrote what value and in what order. When a transaction reads a location, it retrieves the most recent value written by any lower-indexed transaction. This versioning enables validation: after a transaction executes, validation can determine whether the specific transactions that wrote to each read location have changed. Legacy pevm Architecture We made several contributions fine-tuned for EVM. For instance, all EVM transactions in the same block read and write to the beneficiary account for gas payment, making all transactions interdependent by default. RISE pevm addresses this by lazy-updating the beneficiary balance. Rather than writing actual beneficiary balance changes during execution, pevm defers this update at the end of block execution. Similarly, for raw ETH transfers to non-contract addresses, it defers both sender and recipient balance updates. These lazily-accumulated values are accumulated throughout the block and evaluated once at execution completion, or on-demand if explicitly read. However, the legacy architecture has a limitation: it wraps the [revm](https://github.com/bluealloy/revm) EVM implementation as a black box. The custom database interface (VmDB) intercepts reads and writes but cannot optimize the internal execution flow. ### Early Performance Benchmarks Although the legacy pevm is in pre-alpha stage, [early benchmarks](https://github.com/risechain/pevm/tree/main/crates/pevm/benches) already show promising results: * For large blocks with few dependencies, Uniswap swaps saw a 22x improvement in execution speed. * On average, pevm is around 2x faster than typical sequential execution for a variety of Ethereum blocks. * The max speed-up is around 4x for a block with few dependencies. * For L2's with large blocks, pevm is expected to consistently surpass 5x improvement in execution speed. *** ## Future Works: pevm Evolution As we worked on our [continuous block pipeline](/docs/rise-evm/cbp), [shreds](/docs/rise-evm/shreds), and [Reth's parallel sparse trie](https://github.com/paradigmxyz/reth/tree/v1.9.3/crates/trie/sparse), we eventually found ways to innovate Parallel EVM way beyond what BlockSTM originally proposed. The ultimate goal is to achieve 10 Gigagas/s and beyond, making RISE pevm the fastest EVM execution engine available. New pevm Architecture The new architecture aims to accelerate the legacy architecture through the following optimizations. ### Inline Parallel-Aware EVM Rather than wrapping [revm](https://github.com/bluealloy/revm), the new architecture implements an EVM interpreter specifically designed for parallel execution. The inline interpreter sets to minimize VM overheads, and enable efficient sharing of read-only bytecode and state across worker threads. ### Shred Integration As [shreds](/docs/rise-evm/shreds) are becoming more mature, we will add shreds to broadcast pending states per committed transactions in realtime, enabling fullnodes and dApps to observe state changes in realtime. Furthermore, each shred contains a state-diff from the previous state, making it possible for following nodes to build a transaction dependency graph (i.e, DAG), further accelerate re-execution performance. ### Sparse Trie for State Root Calculation Computing the Merkle root of all state changes is computationally expensive and traditionally blocks the critical path. The new design employs a sparse trie to accelerate state root calculation. Rather than computing a state root after all transactions complete, the system progressively updates the trie as transactions validate, with background worker threads computing Merkle proofs in parallel. This reduces the overhead of state root calculation from the critical path, leaving more time for execution. ### Extended Resource-Aware Scheduler We extend the scheduler to also schedule shred committing and multiproof tasks, with a new design that is highly resource-aware. The new scheduler evolves beyond distributing execution and validation tasks to coordinating a richer task set: **execution, validation, multiproof generation, and shred commitment**. It becomes *resource-aware* by analyzing CPU and memory usage, dynamically adjusting task priorities and worker thread assignments. # RiseDB (/docs/rise-evm/rise-db) # RiseDB ## Motivation Every transaction in a blockchain updates state: account balances change or contract updates, and historical records accumulate. Managing this state efficiently is critical to blockchain performance. However, traditional state management architectures face significant bottlenecks that limit throughput and increase latency. The current state storage uses a two-layered architecture. * **Merkle Patricia Trie (MPT)**. An authenticated data structure that guarantees data integrity and is typically built using a Merkle tree-like structure. It also provides a mechanism to verify the presence of specific data within the state. Nodes in the ADS are usually connected via hashes, and each node is stored in the underlying database * **Backend database**. A component that helps persist data to disks; manages physical storage and retrieval; and handles compaction and organization of data. This backend database is often LSM-based. This diagram illustrates the structure:

A read to the state involves traversing the MPT from its root down to the target leaf node. During this traversal, the MPT must fetch the content of each node from the backend database (if not cached). Due to the LSM tree structure of the database, each query itself involves multiple disk accesses. Consequently, a single read operation often translates into many I/O operations. This amplification effect worsens as the state size increases. Simplified Merkle Patricia Trie *A simplified version of the Ethereum’s MPT (source: [CSDN](https://it007.blog.csdn.net/article/details/86551694?spm=1001.2101.3001.6650.19\&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-19-86551694-blog-79992072.pc_relevant_multi_platform_whitelistv1_exp2\&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7Edefault-19-86551694-blog-79992072.pc_relevant_multi_platform_whitelistv1_exp2\&utm_relevant_index=25)). All keys have the same `a7` prefix so an extension node is created to save space and redundancy. The same goes for `a77d337` and `a77d397` which share the same `d3` prefix.* The MPT aims to reduce redundancy and I/Os by compressing nodes with a single child to form so-called extension nodes. As reads need to query all nodes along the path from the root down to the target leaf node, extension nodes help reduce the number of queries. Extension nodes are more effective when the underlying data is sparse. However, as the state grows, the data becoming less sparse, effectively reducing the effects of extension nodes. For a high-performance rollup like RISE targeting 100k+ TPS, this traditional architecture becomes a bottleneck. Every millisecond of latency in state access directly reduces the number of transactions RISE can process per second. *** ## RiseDB RiseDB is a high-performance verifiable key-value store with a specific focus on efficiently managing the state of blockchain networks. One key objective of RiseDB is to achieve significantly faster state updates and access compared to existing solutions. This high throughput is compulsory for blockchains striving to support a large and active user set with numerous transactions. ### Unified Architecture Traditional systems manage world state and Merkle trees separately, creating overhead and redundant operations. RiseDB merges world state and Merkle tree storage into a single, streamlined architecture, eliminating the overhead associated with managing separate layers, leading to more efficient data handling and merkleization processes. This unified design eliminates context switches between different storage engines, reduces data duplication, and streamlines state operations. ### Low Memory Footprint The memory footprint of RiseDB is very small, allowing it to operate efficiently on consumer-grade computers, potentially lowering the barrier to entry for participating in blockchain networks. As a result, it enables broader node participation, reduces operational costs for node operators, and maintains the network's resilience by not requiring expensive infrastructure. Furthermore, the low memory footprint does not come at the cost of performance, RiseDB achieves high-speed operations while remaining memory-efficient. ### SSD-Optimized Access Patterns RiseDB employs a storage design carefully tailored to leverage the strengths of modern SSDs, optimizing access patterns to maximize throughput and durability. By organizing data in a way that minimizes costly random writes and favors sequential, append-only operations, RiseDB efficiently utilizes SSD I/O capabilities. # Shreds (/docs/rise-evm/shreds) # Shreds Shreds enable a user experience that feels like the modern web, but on a blockchain. They generate transaction preconfirmations within single-digit milliseconds, reacting to demand in real time. Unlike traditional blockchains, which wait for discrete blocks to process, Shreds are continuous and interrupt-driven.
Latency with and without Shreds Latency with and without Shreds
## Breaking Down Blocks When posting summaries to L1 and DA, a rollup typically batches multiple L2 blocks together to reduce costs. Since merkleization is not required for every L2 block, we can break L2 blocks (12-second long) into **Shreds** (sub-second long), essentially **mini-blocks without a state root**. Constructing and validating these Shreds is much faster due to the omission of state root merkleization. Therefore, broadcasting them enables rapid transaction and state confirmations. This improved latency doesn't sacrifice security, as new L2 blocks can only provide unsafe confirmations anyway. [//]: # "![Shred flow](/shred_flow.png)" *** ## What are Shreds? Shreds partition an L2 block into multiple consecutive, connecting segments. Each Shred for an L2 block is identified via its sequence number. In other words, `block_num` and `seq_num` together can always identify a Shred. The sequence number increases for each Shred and resets when a new L2 block is constructed. A Shred contains a **ChangeSetRoot** that commits to all state changes made within the Shred. During execution, the sequencer uses the flatDB to access data and records all changes to a **ChangeSet**. The number of entries in a ChangeSet is relatively small compared to the state size because it only holds the changes, therefore the data is sparse and can fit in memory. As a result, constructing the ChangeSetRoot can be efficiently done. *** ## Block Propagation Broadcasting is done per Shred instead of waiting for the full L2 block. Shreds with invalid signatures are discarded. As peer nodes receive valid Shreds, they optimistically construct a local block and provide preconfirmations to users.
Shreds Propagation Shreds Propagation
The sequencer might also broadcast the ChangeSet within a Shred to its peers. This ChangeSet can be verified against the ChangeSetRoot attached to the Shred's header. Nodes trusting the sequencer can apply the ChangeSet immediately to their local state without re-executing transactions. *** ## Batch Preconfirmations Preconfirmations are issued per batch via Shreds instead of per individual transaction. Users can receive preconfirmations without waiting for the entire L2 block to be completed. The Shred blocktime can be configured to balance multiple factors, including preconfirmation latency and network bandwidth. *** ## Efficient Merkleization Merkleization's performance is influenced by both the size of state data and the number of changes (i.e., the size of the ChangeSet). Additionally, batch updates are more efficient than sequential updates. Merkleization for an L2 block only happens after the last Shred is generated. Accumulating changes over multiple Shreds reduces the number of final keys that need updating, thanks to transaction overlap. The same data is likely to be accessed multiple times across blocks, especially with popular dApps like Uniswap. Shreds enable RISE to provide instant transaction confirmations while maintaining the security and consistency guarantees of traditional rollups. # Transaction Lifecycle (/docs/rise-evm/tx-lifecycle) # Transaction Lifecycle RISE's transaction lifecycle facilitates a streamlined process, designed to reduce latency to as low as possible. Unlike other blockchains, RISE aims to provide near-instant responses to users' transactions.
Transaction Lifecycle Transaction Lifecycle
## 1. Transaction Preparation & Broadcasting The lifecycle of a transaction starts with a user creating and signing a transaction, and submitting it to an RPC node via their frontend. ## 2. P2P Propagation After receiving the transaction from the user, the RPC node performs sanity checks and then broadcasts this transaction to the sequencer using the P2P network. ## 3. Mempool Pre-Execution As described in the [CBP](cbp), the transaction is pre-executed as soon as it lands in the sequencer's mempool. The CBP makes use of idle time to execute transactions while other tasks are happening. This is one of the ways we reduce end-to-end latency for transaction processing. ## 4. Shred Inclusion Pending transactions (residing in the mempool) are included in a **Shred**. Shreds partition a canonical L2 block into multiple consecutive yet separately-verifiable segments. Shreds allow an L2 block to be incrementally constructed, with each Shred serving as a batch of preconfirmations for transactions it contains. Importantly, each Shred does not require merkleization, allowing RISE to reduce its latency to just a few milliseconds. Pending transactions are pre-executed while sitting in the mempool, therefore, at the time of Shred inclusion, we can reuse the pre-executed results from the previous step. ## 5. Shred Propagation & Early Updates The sequencer broadcasts a Shred to other nodes via the P2P network after it is created. As soon as a node receives a new Shred, it immediately executes transactions within this Shred (or applies changes provided by the Shred) to get the latest state of the network. This enables faster state synchronization across the network and a quicker response to the user's transaction. At this point, the receipt for the transaction is available and can be returned to the user. ## 6. L2 Block Inclusion After a predefined period of time (L2 blocktime), Shreds are batched together to create a canonical L2 block. At this time, merkleization is done to seal the L2 block. ## 7. L1 Block Inclusion Periodically, L2 blocks are batch-submitted to the DA layer and the L1 for finalizing. At this stage, transactions are considered safe (if no fraud challenge is triggered). ## Key Benefits * **Pre-execution**: Transactions are executed before inclusion, reducing confirmation time * **Shred-based confirmations**: Users get confirmations in milliseconds, not seconds * **Early state updates**: Nodes update state immediately upon receiving Shreds * **Optimized pipeline**: Each step is optimized to minimize latency while maintaining security This lifecycle enables RISE to provide the instant responsiveness users expect while maintaining the security and decentralization properties of a proper rollup. # ZK Fraud Proofs (/docs/rise-evm/zk-fraud-proofs) # ZK Fraud Proofs ## Motivation At the beginning, we adopted an optimistic design primarily due to the simplicity of the optimistic approach and the limitations of zero-knowledge (ZK) proving technology. At that time, simulating an EVM machine with ZK was not feasible, and ZK proving was unable to meet the desired throughput demands. Optimistic rollups, on the other hand, offered a simpler and more scalable solution. ### Complicated Interactive Fraud Proofs However, traditional optimistic rollups rely on **interactive fraud proofs** where validators engage in multiple back-and-forth interactive steps to identify the exact transaction or step where computation diverged. This process is complex to implement correctly, due to: * **Multi-round Interactions**. Parties must engage in multiple rounds of challenges and responses. * **State Management**. Tracking disputed ranges, bisection points, and commitments adds significant complexity. * **Latency Overhead**. Challenge-response cycles can take days or weeks, delaying final settlement. Although interactive fraud proofs work, the process is complex and time-consuming, and requires significant interactions and is unfriendly to challengers. ### Expensive ZK Proofs With recent advancements in the ZK ecosystem, we are now able to prove an EVM block in an order of seconds and transitioning to a full ZK rollup is feasible. Nevertheless, we realize that generating validity proofs is not always ideal. * Validity proofs offer fast finality but the proving performance might not keep up with our execution client on realtime proving. We aim to process 100k TPS at RISE, and ZK proving at this rate is not possible at the moment. * Verifying a validity proof on the L1 is expensive. If we do this frequently, users have to bear this cost, and thus, increase the transaction cost to users. * Furthermore, as long as the sequencer behaves honestly, we will never need to use validity proofs. However, generating validity proofs for every transaction incurs an additional cost for users. ### Inability of DA Commitment Challenging with AltDA Data Availability (DA) is crucial to fraud games, and the security of a rollup. Without DA, it is not possible to ensure the challenge and sequencer are playing a game on the same data. RISE's current design is built upon the battle-tested [OP Stack](https://github.com/ethereum-optimism/optimism) and leverages [EigenDA for its Data Availability (DA) layer](/docs/rise-evm/da). However, this design has a critical security issue that makes the sequencer highly trusted, discouraging users to use RISE. When rollups use an AltDA layer instead of posting all data to Ethereum L1, they introduce a new challenge: **verifying DA commitments on-chain becomes problematic**. OP Stack supports two AltDA systems: **Type 0 (Keccak)** commitments, which are simple hashes and can be challenged directly on-chain, and **Type 1 (DA-Service)** commitments, which have a flexible `da_layer_byte ++ payload` structure designed to be handled by the AltDA Server. The problem is, the existing OP Stack implementation [does not support AltDA challenge other than Type 0 (Keccak)](https://specs.optimism.io/experimental/alt-da.html?highlight=altDA#data-availability-challenge-contract). This creates a verification gap, the network must trust the AltDA Server's claim about data availability, making fraud proofs not possible, which in turn violates the security of the rollup. *** Therefore, we consider a hybrid approach in which we still adopt the optimistic design but utilizing ZK to generate validity proofs for a state commitment if challenged. *** ## ZK Fraud Proofs with OP Succinct Lite RISE's ZK Fraud Proofs are made possible by [OP Succinct Lite](https://github.com/succinctlabs/op-succinct). OP Succinct Lite allow us to resolve any dispute in a single round, without the interaction requirement between the challengers and the proposer. This is more efficient than the interactive bisecting game mentioned above. Moreover, OP Succinct Lite supports AltDA like EigenDA or Celestia, which is the perfect match for RISE. ### The Workflow The following diagram depicts the simplified flow of the hybrid approach. The sequencer publishes state commitments without proof similar to traditional optimistic rollups. If anyone detects an invalid state commitment, they can initiate a fraud challenge. Once challenged, the sequencer is responsible for generating a ZK validity proof demonstrating that the state transition was correct. Failures in providing a valid ZK proof in time will lead the sequencer to be slashed, and the corresponding commitment is considered invalid. The system's elegance lies in its economics: **most of the time (99.9999%), validity proofs are never needed**. This means users avoid bearing the costs associated with proof generation and verification. ### Implications The ZK fraud proof approach offers a more efficient, secure, and user-friendly experience. * **Shorter Challenge Period**. Validity proofs are only required once we have a challenge. If a challenge is invoked, the sequencer then has an additional window to submit the required validity proof. The additional window time should be on an order of the maximum proving time for the sake of security. * **Simpler and Robust Fraud Mechanism**. ZK proofs appear to be more robust than interactive fraud proofs and there are several ZK rollups that have been running on the mainnet. With this approach, a challenger can just focus on keeping up with the chain progress and identifying the incorrect state transition (same as the re-executing fraud proofs), no other interaction is required. * **Cost Saving**. The cost for users is the same as in an optimistic rollup and operational costs are lower than a ZK rollup. While ZK rollups have to bear the cost of generating validity proofs for every state transition, even if there is no transaction; this is not required in a hybrid mode. As a result, users do not have to pay extra costs of validity proof generation and verification. * **AltDA**. OP Succinct Lite's support for EigenDA means RISE can achieve the cost and scalability benefits of off-chain DA while maintaining on-chain verifiability through ZK proofs. No trust on the sequencer is required for security. *** ## The Path Forward: ZK Rollup RISE is designed to evolve toward a full ZK rollup as ZK proving technology matures and becomes more cost-effective. Rather than attempting to jump directly to pure ZK proving, we follow a phased approach that allows us to validate performance, optimize systems, and maintain user experience at each stage. ### Phase 1: ZK Fraud Proofs (Current) RISE currently operates as a hybrid rollup using ZK fraud proofs for security. The sequencer publishes state commitments optimistically, and ZK proofs are generated only when disputes arise. This phase delivers fast, low-cost transactions in the honest case, cryptographic security guarantees through on-demand ZK proving, and economic efficiency where users do not bear proving costs for normal operation. ### Phase 2: Proactive Proving ***Figure**. Lifecycle of a fraud game.* (\*: *resolve can only be processed if the parent game is already resolved*). As ZK proving technology continues to advance and costs decrease, RISE will transition to **proactive proving**, where the sequencer voluntarily submits ZK proofs for state commitments even without fraud challenges. This is a critical transitional phase for several reasons. First, transactions achieve cryptographic finality as soon as the proof is verified on L1, rather than waiting for a challenge window to pass, delivering faster finality to users. Second, this phase allows RISE to operationally validate ZK proving performance at scale without being dependent on it for security: running proofs continuously reveals performance bottlenecks and allows optimization before full ZK commitments. Third, users experience faster finality incrementally without a sudden transition. In this phase, fraud proof challenges still serve as a security backup: if a sequencer fails to submit a proof, the fraud proof mechanism activates. This provides a graceful fallback while allowing real-world testing of proving infrastructure at scale. ### Phase 3: Full ZK Rollup Once proving costs are sufficiently reduced and performance meets RISE's throughput demands, the network will transition to a **full ZK rollup**. At this point, validity proofs become mandatory for every state transition, delivering instant cryptographic finality as standard. The fraud proof mechanism is no longer needed since cryptographic correctness is always proven. Security is guaranteed cryptographically rather than through economic assumptions. The transition between phases is not time-bound but rather tied to technological maturity and cost-effectiveness of ZK proving. RISE will remain in Phase 1 until Phase 2 becomes practical, and will remain in Phase 2 until Phase 3 becomes economically viable. This phased approach ensures RISE maintains optimal performance and user experience at every stage of evolution. # Connecting Wallet (/docs/rise-wallet/connecting) import { WalletConnect } from "@/components/rise-wallet/WalletConnect"; import { ComponentPreviewTabs } from "@/components/rise-wallet/ComponentPreviewTabs"; import { CODE_EXAMPLES } from "@/components/rise-wallet/code-examples"; # Connecting Wallet Connecting to RISE Wallet is as simple as using any standard Wagmi connector. The Porto connector handles passkey authentication automatically, creating a seamless login experience. ## How it Works 1. **Find the Rise Wallet Connector**: Locate the Rise Wallet connector from the available connectors using its ID `com.risechain.wallet`. 2. **Trigger Connection**: Call the `connect` function with the Rise Wallet connector. 3. **Passkey Authentication**: The user is prompted to authenticate using their device's passkey (FaceID, TouchID, etc.). 4. **Account Ready**: Once authenticated, the account is connected and ready to transact. The Rise Wallet connector integrates seamlessly with Wagmi's standard hooks like `useAccount`, `useConnect`, and `useDisconnect`, requiring no specialized APIs. # How It Works (/docs/rise-wallet/how-it-works) import { Steps, Step } from 'fumadocs-ui/components/steps'; RISE Wallet is built on top of [Porto](https://porto.sh/), a next-generation account stack for Ethereum that leverages [EIP-7702](https://eip7702.io/) for native account abstraction. With our backend & SDK customised to utilise RISE high performance in addition to providing chain wide gas sponsorship for real users. This page explains how RISE Wallet works under the hood. ## Architecture Overview RISE Wallet consists of three main components: ### 1. Smart Account Infrastructure * **Porto Smart Accounts**: Audited contracts that provide account abstraction features * **EIP-7702 Integration**: Native account abstraction without smart contract wallets * **Key Management**: Support for multiple key types (P256, `secp256k1`, WebAuthn) ### 2. Relay Infrastructure * **Gas Sponsorship**: Automatic gas payment for eligible transactions * **Transaction Batching**: Bundle multiple operations into single atomic transactions * **Circuit Breakers**: Safety mechanisms to prevent abuse and overspending ### 3. Dialog Interface * **Passkey Authentication**: WebAuthn-based login with biometrics * **Cross-Platform Support**: Works on web, mobile, and desktop * **Session Management**: Secure storage and handling of session keys ## Transaction Flow When a user interacts with RISE Wallet, the following process occurs: ### User Action User initiates a transaction (transfer, swap, mint, etc.) ### Wallet Dialog\* RISE Wallet dialog opens to handle the request When a valid session key is available, this step is bypassed entirely. The transaction is signed automatically without requiring user interaction, enabling seamless high-frequency actions. ### Authentication Check The system checks if a valid session key exists: * **Has Session Key** → Sign automatically with session key * **No Session Key** → Request user signature via passkey ### Relay Processing The signed transaction is sent to the RISE Relay for processing ### Sponsorship Validation Relay checks sponsorship rules: * User tier and daily limits * Whitelisted contracts * Allowed functions ### Network Submission Valid transaction submitted to RISE network ### Instant Confirmation Shred confirmation received in \~3ms via WebSocket ## Key Technologies ### EIP-7702: Set Code Transaction EIP-7702 allows EOAs (Externally Owned Accounts) to temporarily delegate their functionality to smart contract code during a transaction. This enables account abstraction features without deploying a smart contract wallet, while maintaining compatibility with existing Ethereum infrastructure. Read more about EIP-7702 [here](https://eip7702.io/). ### WebAuthn & Passkeys RISE Wallet uses WebAuthn for authentication, supporting biometric login methods like FaceID, TouchID, and Windows Hello. Keys are stored securely in the device's secure enclave and can sync across devices via cloud providers. ### Session Keys Temporary keys with specific permissions enable apps to act on a user's behalf within defined limits. This allows high-frequency actions without user interruption, while maintaining safety through time and spend limits. ## Gas Sponsorship RISE Wallet implements intelligent gas sponsorship: ### Default Sponsorship * New users receive daily gas budget * Core protocol interactions (swaps, mints) are sponsored * RISEx trading is fully sponsored ## Security and Recovery RISE Wallet employs multi-layer security with passkeys stored in secure hardware, audited Porto contracts, and RISE's fast finality to prevent reorg attacks. Session keys use time-bound permissions with explicit scoping for safe temporary access. Account recovery is supported through guardian recovery with trusted addresses, time-locked recovery mechanisms for added security, and multi-signature options for high-value accounts. ## Integration with RISE RISE Wallet is optimized for RISE's unique architecture: ### Shred Integration * Instant confirmation notifications via WebSocket * Realtime balance updates * Immediate transaction feedback ## Technical Specifications ### Supported Chains * RISE Mainnet (coming soon) * RISE Testnet * Future: Cross-chain support via RISE bridges ### Key Types * **P256**: Native browser/device support * **`secp256k1`**: Ethereum standard compatibility * **WebAuthn P256**: Passkey integration {/* ### Performance - Transaction preparation: <100ms - Signature generation: <50ms - Network submission: <10ms - Total UX latency: <200ms + network time */} ## Learn More * [Porto Documentation](https://porto.sh/) - Original Porto implementation * [EIP-7702 Specification](https://eips.ethereum.org/EIPS/eip-7702) - Set code transaction details # RISE Wallet (/docs/rise-wallet) import { RiseWalletPlaygroundSimple } from "@/components/rise-wallet/RiseWalletPlaygroundSimple"; import { Card, Cards } from 'fumadocs-ui/components/card'; import { Cpu, Code, Key, Coins, ArrowLeftRight } from 'lucide-react'; RISE Wallet Stack is a chain-native wallet layer for the entire RISE ecosystem. It is a shared, trustless walet experience that feels like Web2, backed by audited smart accounts from Porto and wired into RISE's ultra-fast EVM. ## Why RISE Wallet? Most of today's wallets were built for early crypto power users, requiring seed phrases, browser extensions, manual gas management, and complex bridging. RISE Wallet takes a different path: * **Gasless by Default**: Users receive a daily gas budget. You don't need to hold ETH to start using apps. * **Passkey Login**: No seed phrases. Sign in with biometrics (FaceID, TouchID) or WebAuthn. * **One Wallet, Everywhere**: A single global wallet experience that spans the entire RISE ecosystem. * **Session Keys**: Delegate specific permissions (e.g., "spending 50 tokens/minute") to local keys for instant, popup-free transactions. ## Try It Out Connect your wallet below to experience the RISE Wallet flow. You can create a session key, mint test tokens, and swap them instantly. ## Features ### Chain-Native Integration RISE Wallet is wired directly into RISE's infrastructure, providing a seamless experience across all applications on the chain. ### Built for Speed Leveraging RISE's 3ms confirmations via shreds, wallet operations feel instant. ### Developer-First Simple SDK integration with standard `Wagmi` and `Viem` libraries means you can add wallet functionality without learning specialized APIs. ## Next Steps } title="How It Works" href="/docs/rise-wallet/how-it-works" description="Technical architecture and implementation details" /> } title="Viem Integration" href="/docs/rise-wallet/viem" description="Integrate with Viem for direct contract interactions" /> } title="Wagmi Integration" href="/docs/rise-wallet/wagmi" description="Add RISE Wallet to your React app with Wagmi" /> } title="Session Keys" href="/docs/rise-wallet/session-keys" description="Enable gasless, popup-free transactions" /> } title="Minting Example" href="/docs/rise-wallet/minting" description="Mint tokens with RISE Wallet integration" /> } title="Swapping Example" href="/docs/rise-wallet/swapping" description="Build token swap functionality with session keys" /> # Minting Tokens (/docs/rise-wallet/minting) import { MintWidget } from "@/components/rise-wallet/MintWidget"; import { ComponentPreviewTabs } from "@/components/rise-wallet/ComponentPreviewTabs"; import { CODE_EXAMPLES } from "@/components/rise-wallet/code-examples"; # Minting Tokens This example demonstrates how to interact with a smart contract on RISE. Because RISE Wallet users often start without gas, transactions are sponsored by the relay infrastructure. ## How it Works 1. **Contract Interaction**: We use `wagmi`'s `useSendCalls` (or a wrapper hook) to send transactions. 2. **Gas Sponsorship**: The RISE Paymaster automatically sponsors eligible transactions. 3. **User Experience**: The user signs the request with their Passkey (or Session Key if active), and the transaction is submitted instantly. ### Sending a Transaction To write to a contract, encode the function data using `viem` and send it using `sendCallsAsync`. ```tsx import { useSendCalls } from "wagmi"; import { encodeFunctionData } from "viem"; import { MintableERC20ABI } from "@/abi/erc20"; // ... inside your component const { sendCallsAsync } = useSendCalls(); const handleMint = async () => { const data = encodeFunctionData({ abi: MintableERC20ABI, functionName: "mintOnce", args: [], }); await sendCallsAsync({ calls: [ { to: "0x...", // Token Contract Address data, }, ], }); }; ``` # Session Keys (/docs/rise-wallet/session-keys) import { SessionKeysWidget } from "@/components/rise-wallet/SessionKeysWidget"; import { ComponentPreviewTabs } from "@/components/rise-wallet/ComponentPreviewTabs"; import { CODE_EXAMPLES } from "@/components/rise-wallet/code-examples"; # Session Keys Session keys are temporary keys that are granted specific permissions. They allow your app to sign transactions on behalf of the user without prompting them for confirmation every time. This is critical for: * **High-frequency trading**: Place and cancel orders instantly. * **Games**: Move characters or perform actions without interrupting gameplay. * **Social apps**: Like posts or follow users with a single tap. ## Creating a Session Key You use the `useGrantPermissions` hook from `rise-wallet/wagmi` to request a new session key. You must define exactly what this key can do (permissions) and how long it lasts (expiry). ```tsx import { Hooks } from "rise-wallet/wagmi"; import { P256, PublicKey } from "ox"; // ... inside component const grantPermissions = Hooks.useGrantPermissions(); const createSession = async () => { // 1. Generate a local key pair (P256) const privateKey = P256.randomPrivateKey(); const publicKey = PublicKey.toHex(P256.getPublicKey({ privateKey }), { includePrefix: false, }); // 2. Define Permissions (e.g., allow calling 'mint' on a specific contract) const permissions = { calls: [ { to: "0x...", // Contract Address signature: "0x...", // Function Selector } ] }; // 3. Request the session key from the wallet await grantPermissions.mutateAsync({ key: { publicKey, type: "p256" }, expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour permissions, }); // 4. Store the private key securely (e.g., localStorage) to sign future requests localStorage.setItem("session_key", privateKey); }; ``` Once created, you can use the stored private key to sign and send `wallet_sendPreparedCalls` requests directly to the RISE RPC, bypassing the wallet popup completely. # Swapping Tokens (/docs/rise-wallet/swapping) import { SwapWidget } from "@/components/rise-wallet/SwapWidget"; import { ComponentPreviewTabs } from "@/components/rise-wallet/ComponentPreviewTabs"; import { CODE_EXAMPLES } from "@/components/rise-wallet/code-examples"; # Swapping Tokens Swapping tokens often requires two steps: **Approval** and **Execution**. RISE Wallet supports batching these operations into a single atomic transaction when using EIP-5792 `sendCalls`, providing a much smoother UX. ## Atomic Batching Instead of asking the user to sign an "Approve" transaction, wait for it to land, and then sign a "Swap" transaction, you can bundle them together. ```tsx const approveData = encodeFunctionData({ abi: ERC20_ABI, functionName: "approve", args: [ROUTER_ADDRESS, amount], }); const swapData = encodeFunctionData({ abi: ROUTER_ABI, functionName: "swapExactTokensForTokens", args: [amount, minOut, path, recipient, deadline], }); // Send both calls in one user operation await sendCallsAsync({ calls: [ { to: TOKEN_ADDRESS, data: approveData }, { to: ROUTER_ADDRESS, data: swapData }, ], }); ``` This reduces the time users spend waiting and clicking popups, making DeFi feel instant. # Viem Integration (/docs/rise-wallet/viem) import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; This guide covers integrating RISE Wallet with [Viem](https://viem.sh/), a TypeScript interface for Ethereum that provides low-level access to blockchain functionality. ## Overview RISE Wallet provides first-class Viem support through custom actions and account implementations. This integration is ideal for developers who need fine-grained control over wallet operations. ## Installation ```bash npm i rise-wallet viem ``` ```bash pnpm add rise-wallet viem ``` ```bash yarn add rise-wallet viem ``` ```bash bun add rise-wallet viem ``` ## Basic Usage ### Creating a Client Use Rise Wallet's EIP-1193 provider with Viem's custom transport: ```ts import { createClient, custom } from "viem"; import { RiseWallet } from "rise-wallet"; // Create Rise Wallet instance const rw = RiseWallet.create(); // Create Viem client with Rise Wallet provider const client = createClient({ transport: custom(rw.provider), }); ``` ### Using Wallet Actions RISE Wallet exports a `WalletActions` module with Rise Wallet-specific functionality: ```ts import { WalletActions } from "rise-wallet/viem"; // Connect to wallet const { accounts } = await WalletActions.connect(client); console.log("Connected account:", accounts[0]); // Get user assets const assets = await WalletActions.getAssets(client); console.log("User assets:", assets); ``` ## Standard Viem Actions All standard Viem wallet actions work with RISE Wallet: ### Send Transaction ```ts import { parseEther } from "viem"; import * as Actions from "viem/actions"; const hash = await Actions.sendTransaction(client, { to: "0x...", value: parseEther("0.001"), }); console.log("Transaction hash:", hash); ``` ### Send Calls (EIP-5792) ```ts const result = await Actions.sendCalls(client, { account: "0x...", calls: [ { to: "0x...", data: "0x...", }, ], }); ``` ### Sign Message ```ts const signature = await Actions.signMessage(client, { account: "0x...", message: "Hello RISE!", }); ``` ## RISE Wallet Actions ### Grant Permissions Create session keys with specific permissions: ```ts import { Key } from "rise-wallet/viem"; import { keccak256, toHex, parseEther } from "viem"; const permissions = await WalletActions.grantPermissions(client, { key: Key.createP256(), // Generate new P256 key expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour permissions: { calls: [ { to: "0x...", // Contract address signature: keccak256(toHex("transfer(address,uint256)")).slice(0, 10), } ], spend: [ { limit: parseEther("50"), period: "minute", token: "0x...", } ], }, }); ``` ### Get Permissions View active permissions: ```ts const permissions = await WalletActions.getPermissions(client); permissions.forEach(permission => { console.log("Key:", permission.key); console.log("Expiry:", permission.expiry); console.log("Allowed calls:", permission.calls); }); ``` ### Revoke Permissions Remove granted permissions: ```ts await WalletActions.revokePermissions(client, { keyId: "0x...", }); ``` ## Key Management RISE Wallet provides utilities for working with different key types: ### P256 Keys ```ts import { Key, PublicKey } from "rise-wallet/viem"; // Create new P256 key const key = Key.createP256(); // Import existing key const imported = Key.fromP256({ privateKey: "0x...", }); // Sign with key const signature = await Key.sign({ payload: "0x...", privateKey: key.privateKey, }); ``` ### WebAuthn Keys ```ts // Create WebAuthn credential const credential = await Key.createWebAuthnP256({ user: { name: "alice@example.com", displayName: "Alice", }, }); // Use for signing const webAuthnKey = Key.fromWebAuthnP256({ credential, publicKey: credential.publicKey, }); ``` ## Advanced Patterns ### Direct Provider Access For maximum control, access the provider directly: ```ts const provider = await rw.provider; // Use JSON-RPC methods directly const result = await provider.request({ method: "wallet_prepareCalls", params: [{ calls: [{ to: "0x...", data: "0x...", }], chainId: "0x...", from: "0x...", }], }); ``` ### Session Key Signing Sign transactions with session keys: ```ts import { P256, Signature } from "ox"; // Prepare transaction const { digest, ...request } = await provider.request({ method: "wallet_prepareCalls", params: [{ calls: [...], key: { publicKey, type: "p256" }, }], }); // Sign with session key const signature = Signature.toHex( P256.sign({ payload: digest, privateKey }) ); // Send signed transaction const result = await provider.request({ method: "wallet_sendPreparedCalls", params: [{ ...request, signature }], }); ``` ### Custom Actions Create your own actions by extending Viem: ```ts import { type Client } from "viem"; export async function myCustomAction(client: Client, params: any) { return client.request({ method: "my_custom_method", params, }); } // Use with client.extend() const extendedClient = client.extend(() => ({ myCustomAction, })); await extendedClient.myCustomAction({ ... }); ``` ## WebSocket Support For realtime updates, use WebSocket transport: ```ts import { createPublicClient, webSocket } from "viem"; import { riseTestnet } from "viem/chains"; const wsClient = createPublicClient({ chain: riseTestnet, transport: webSocket("wss://testnet.riselabs.xyz/ws"), }); // Watch for events in realtime wsClient.watchContractEvent({ address: "0x...", abi: [...], eventName: "Transfer", onLogs: (logs) => { console.log("Transfer events:", logs); }, }); ``` ## Type Safety RISE Wallet provides comprehensive TypeScript types: ```ts import type { PermissionsRequest } from "rise-wallet/viem"; // All actions are fully typed const permissions: PermissionsRequest = { key: { publicKey: "...", type: "p256" }, expiry: 123456789, permissions: { calls: [{ to: "0x..." as const, signature: "0x..." as const, }], }, }; ``` ## Error Handling Handle Viem errors appropriately: ```ts import { BaseError } from "viem"; try { await Actions.sendTransaction(client, { ... }); } catch (error) { if (error instanceof BaseError) { console.error("Error name:", error.name); console.error("Error details:", error.details); console.error("Error version:", error.version); } } ``` ## Best Practices ### 1. Use Action Modules Import actions from their respective modules for better tree-shaking: ```ts // Good import { sendTransaction } from "viem/actions"; import { connect } from "rise-wallet/viem/WalletActions"; // Less optimal import * as Actions from "viem/actions"; ``` ### 2. Handle Account State Always check account connection before operations: ```ts const accounts = await client.request({ method: "eth_accounts" }); if (!accounts.length) { // Request connection first await WalletActions.connect(client); } ``` ### 3. Optimize Gas Use Viem's gas utilities: ```ts const gasPrice = await client.getGasPrice(); const gasEstimate = await client.estimateGas({ to: "0x...", data: "0x...", }); ``` ## Examples * [Basic Viem Usage](https://github.com/risechain/wallet-demo) * [Session Key Implementation](/docs/rise-wallet/getting-started/session-keys) * [Contract Interactions](/docs/rise-wallet/getting-started/minting) ## Related Documentation * [Viem Documentation](https://viem.sh/) * [Wagmi Integration](/docs/rise-wallet/integration/wagmi) * [Porto Viem Docs](https://porto.sh/sdk/viem) # Wagmi Integration (/docs/rise-wallet/wagmi) import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; This guide covers integrating RISE Wallet with [Wagmi](https://wagmi.sh/), the popular collection of React Hooks for Ethereum. ## Overview RISE Wallet implements a Wagmi connector and provides custom React hooks that map directly to the wallet's capabilities. The integration is designed to feel natural to developers already familiar with Wagmi. ## Installation ```bash npm i rise-wallet wagmi viem @tanstack/react-query ``` ```bash pnpm add rise-wallet wagmi viem @tanstack/react-query ``` ```bash yarn add rise-wallet wagmi viem @tanstack/react-query ``` ```bash bun add rise-wallet wagmi viem @tanstack/react-query ``` ## Basic Setup ### 1. Configure the Rise Wallet Connector ```ts title="config/wagmi.ts" import { Chains, RiseWallet } from "rise-wallet"; import { riseWallet } from "rise-wallet/wagmi"; import { createConfig, http } from "wagmi"; // Export the connector for advanced usage export const rwConnector = riseWallet(RiseWallet.defaultConfig); // Create wagmi config export const config = createConfig({ chains: [Chains.riseTestnet], connectors: [rwConnector], transports: { [Chains.riseTestnet.id]: http("https://testnet.riselabs.xyz"), }, }); ``` ### 2. Set Up Providers ```tsx title="app/providers.tsx" "use client"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { WagmiProvider } from "wagmi"; import { config } from "@/config/wagmi"; import { useState } from "react"; export function Providers({ children }: { children: React.ReactNode }) { const [queryClient] = useState(() => new QueryClient()); return ( {children} ); } ``` ### 3. Wrap Your App in the Provider Add the `Providers` component to your root layout: ```tsx title="app/layout.tsx" import { Providers } from "./providers"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` ## Standard Wagmi Hooks All standard Wagmi hooks work seamlessly with RISE Wallet: ### Connection Management ```tsx title="components/WalletButton.tsx" import { useConnect, useConnection, useDisconnect, useConnectors } from "wagmi"; export function WalletButton() { const { address, isConnected } = useConnection(); const connect = useConnect(); const disconnect = useDisconnect(); const connectors = useConnectors(); if (isConnected) { return (
{address}
); } const rwConnector = connectors.find(c => c.id === "com.risechain.wallet"); if (!rwConnector) return null; return ( ); } ``` ### Sending Transactions ```tsx title="components/SendTransaction.tsx" import { useSendCalls } from "wagmi"; import { parseEther } from "viem"; export function SendTransaction() { const sendCalls = useSendCalls(); const handleSend = async () => { const result = await sendCalls.mutateAsync({ calls: [{ to: "0x...", value: parseEther("0.001"), }], }); console.log("Transaction sent:", result); }; return ( ); } ``` ## RISE Wallet Specific Hooks Import custom hooks via the `Hooks` namespace: ```tsx import { Hooks } from "rise-wallet/wagmi"; // or individually import { useGrantPermissions } from "rise-wallet/wagmi/Hooks"; ``` ### useGrantPermissions Create session keys with specific permissions: ```tsx const grantPermissions = Hooks.useGrantPermissions(); const createSession = async () => { await grantPermissions.mutateAsync({ key: { publicKey: "...", type: "p256" }, expiry: Math.floor(Date.now() / 1000) + 3600, // 1 hour permissions: { calls: [{ to: "0x...", signature: "0x...", }], }, }); }; ``` ### usePermissions View current permissions for the connected account: ```tsx const { data: permissions } = Hooks.usePermissions(); if (permissions) { console.log("Active permissions:", permissions); } ``` ### useRevokePermissions Revoke previously granted permissions: ```tsx const revokePermissions = Hooks.useRevokePermissions(); const revokeSession = async (keyId: string) => { await revokePermissions.mutateAsync({ keyId, }); }; ``` ### useAssets Get user's token balances and assets: ```tsx const { data: assets } = Hooks.useAssets(); return (
{assets?.map(asset => (
{asset.symbol}: {asset.balance}
))}
); ``` ## Advanced Patterns ### Batched Transactions Execute multiple operations atomically: ```tsx const sendCalls = useSendCalls(); // Approve and swap in one transaction await sendCalls.mutateAsync({ calls: [ { to: TOKEN_ADDRESS, data: approveCalldata, }, { to: DEX_ADDRESS, data: swapCalldata, }, ], // Atomic execution - all succeed or all fail atomicRequired: true, }); ``` ### Reading Transaction Status Monitor transaction progress: ```tsx import { useWaitForTransactionReceipt } from "wagmi"; const { data: receipt, isLoading } = useWaitForTransactionReceipt({ hash: transactionHash, }); if (receipt) { console.log("Transaction confirmed:", receipt); } ``` ### Error Handling Properly handle wallet errors: ```tsx import { BaseError } from "wagmi"; const sendCalls = useSendCalls(); try { await sendCalls.mutateAsync({...}); } catch (error) { if (error instanceof BaseError) { // Handle specific error types if (error.shortMessage.includes("rejected")) { console.log("User rejected the request"); } } } ``` ## TypeScript Support RISE Wallet provides full TypeScript support: ```tsx import type { RiseWallet } from "rise-wallet"; import type { Config } from "wagmi"; // Config is fully typed const config: Config = createConfig({ chains: [Chains.riseTestnet], connectors: [rwConnector], transports: { [Chains.riseTestnet.id]: http(), }, }); ``` ## Best Practices ### 1. Check Connection State Always verify the connection state before attempting transactions: ```tsx const { isConnected } = useConnection(); if (!isConnected) { return ; } ``` ### 2. Handle Loading States Provide feedback during async operations: ```tsx const sendCalls = useSendCalls(); // Access: sendCalls.isPending, sendCalls.isSuccess, sendCalls.isError ``` ### 3. Use Error Boundaries Wrap your app with error boundaries to catch unexpected errors: ```tsx import { ErrorBoundary } from "react-error-boundary"; }> ``` ## Examples For complete working examples, check out: * [Basic Integration](https://github.com/risechain/wallet-demo) * [Session Keys Demo](https://wallet-demo-nine.vercel.app/) * [DeFi Integration](/docs/rise-wallet/getting-started/swapping) ## Related Documentation * [Wagmi Documentation](https://wagmi.sh/) * [Viem Integration](/docs/rise-wallet/integration/viem) * [Session Keys Guide](/docs/rise-wallet/getting-started/session-keys) # RISEx (/docs/risex) import { Card, Cards } from 'fumadocs-ui/components/card'; import { Activity, FileCode } from 'lucide-react'; RISEx delivers EVM-based perps with institutional-grade performance and synchronous composability with all of DeFi. Fully onchain orderbooks ensure verifiability, and ultra low latency ensures orders are executed instantly with precision, meaning tight spreads and low slippage. ## The Home for Global Markets RISEx is the flagship Integrated Perps DEX built on RISE. It represents the first instantiation of **RISE MarketCore**, demonstrating that high-performance orderbooks can live fully onchain while remaining composable with the broader DeFi ecosystem. By leveraging the RISE EVM's continuous execution pipeline and sub-50ms block times, RISEx offers a trading experience comparable to centralized exchanges but with the transparency and trustlessness of a decentralized network. ### Key Highlights * **Unmatched Performance**: Operates in a sub-3ms execution with a target of 100k TPS. * **Synchronous Composability**: Orderbooks share state with AMMs, lending markets, and vaults in the same block, enabling capital efficiency previously impossible on isolated app-chains. * **Seamless UX**: Features CEX-like APIs, sponsored gas for traders, and instant execution. * **Powered by Experience**: Accelerated by the acquisition of BSX Labs, bringing institutional-grade matching engine expertise to the RISE ecosystem. ## Documentation } title="API" href="/docs/risex/api/" description="REST API documentation with examples for trading, order management, and account operations" /> } title="Contract" href="/docs/risex/contracts/deployments" description="Smart contract interfaces, deployment addresses, and on-chain interaction guides" /> ## Public Testnet Public testnet live at: [testnet.rise.trade](https://testnet.rise.trade/) *** **Last updated:** December 20, 2025 # Remix IDE (/docs/builders/smart-contracts/remix) import { Step, Steps } from 'fumadocs-ui/components/steps'; [Remix IDE](https://remix.ethereum.org) is a browser-based development environment for writing, compiling, and deploying smart contracts. No installation required. ## Prerequisites * A Web3 wallet (MetaMask, Rabby or similar) with RISE Testnet configured * Testnet ETH from the [RISE Faucet](https://faucet.testnet.riselabs.xyz/) ## Deploy a Contract ### Create the Contract Open [Remix IDE](https://remix.ethereum.org) and create a new file called `Counter.sol` in the File Explorer. Add the following code: ```solidity title="Counter.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.30; contract Counter { uint256 public number; function setNumber(uint256 newNumber) public { number = newNumber; } function increment() public { number++; } } ``` ### Compile the Contract 1. Click the **Solidity Compiler** tab (in the left sidebar) 2. Select compiler version `0.8.30` or higher 3. Click **Compile Counter.sol** Enable **Auto compile** in the compiler settings for a better development experience. Your contracts will automatically compile as you make changes. ### Deploy to RISE Testnet 1. Click the **Deploy & Run Transactions** tab (in the left sidebar) 2. In the **Environment** dropdown, select **Injected Provider - MetaMask (or whichever wallet you have)** 3. Your wallet will prompt you to connect - approve the connection 4. **Important**: Make sure your wallet is connected to RISE Testnet (Chain ID: 11155931) 5. Ensure `Counter` is selected in the **Contract** dropdown 6. Click **Deploy** 7. Confirm the transaction in your wallet ### Interact with the Contract Once deployed, your contract will appear under **Deployed Contracts**: * Click `number` to read the current value (starts at 0) * Enter a value and click `setNumber` to set a new number * Click `increment` to increase the number by 1 Each write operation (`setNumber`, `increment`) will require a transaction confirmation in your wallet. ## View on Explorer After deployment, you can view your contract on the [RISE Testnet Explorer](https://explorer.testnet.riselabs.xyz) by searching for the contract address shown in Remix. # API Methods (/docs/builders/shreds/api-methods) # API Methods Core methods that power RISE's realtime capabilities. For standard Ethereum methods, visit the [Ethereum JSON-RPC Documentation](https://ethereum.org/en/developers/docs/apis/json-rpc/). ## eth\_sendRawTransactionSync Sends a signed transaction and waits for instant confirmation, returning the complete transaction receipt. This method is based on [EIP-7966](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7966.md), which introduces synchronous transaction confirmation to Ethereum. RISE implements this standard to provide instant transaction finality. ### Parameters 1. `data` - Signed transaction data (hex string) ### Returns Complete `TransactionReceipt` object with all standard fields: ```typescript { transactionHash: string, blockNumber: string, blockHash: string, transactionIndex: string, from: string, to: string | null, gasUsed: string, cumulativeGasUsed: string, status: string, // "0x1" for success, "0x0" for failure logs: Log[], logsBloom: string, contractAddress: string | null } ``` ### Example ```typescript const receipt = await client.request({ method: 'eth_sendRawTransactionSync', params: ['0x...signed_transaction'] }) // Transaction is already confirmed! console.log('Transaction hash:', receipt.transactionHash) console.log('Status:', receipt.status === '0x1' ? 'Success' : 'Failed') ``` ### Using with viem ```typescript import { createWalletClient, http } from 'viem' import { shredActions } from 'shreds/viem' import { riseTestnet } from 'viem/chains' const client = createWalletClient({ chain: riseTestnet, transport: http() }).extend(shredActions) // Send transaction with instant preconfirmation receipts const receipt = await client.sendTransactionSync({ to: '0x...', value: parseEther('1.0') }) console.log('Transaction confirmed:', receipt) ``` ### Key Benefits * **Instant confirmation**: No waiting for block inclusion * **Complete receipt**: All transaction details immediately available * **Synchronous flow**: Simplifies application logic * **Error handling**: Failed transactions return immediately with status `0x0` ## Usage Patterns ### Synchronous Transaction Flow ```typescript import { sendTransactionSync } from 'shreds/viem' // Traditional async pattern (not needed with RISE) // const hash = await client.sendTransaction(tx) // const receipt = await client.waitForTransactionReceipt({ hash }) // RISE synchronous pattern const receipt = await sendTransactionSync(client, tx) // Transaction already confirmed! ``` ## Performance Characteristics ### RTT (Round-Trip Time) Targets * **Shred Confirmation**: 3-5ms (p50), 10ms (p99) * **Event Delivery**: \< 10ms from transaction execution * **State Updates**: Immediate upon shred confirmation ### Throughput * **Transactions per Shred**: 1-100 * **Shreds per Second**: 1000+ * **Total TPS**: 10,000+ ## Best Practices ### 1. Use Synchronous Methods for Critical Operations ```typescript // Preferred for payments and time-sensitive operations const receipt = await client.sendTransactionSync(tx) ``` ### 2. Handle Errors Immediately ```typescript try { const receipt = await client.sendTransactionSync(tx) } catch (error) { // Handle failure immediately - no need to wait console.error('Transaction failed:', error) } ``` ## Next Steps * [Watching Events](/docs/builders/shreds/watching-events) - Realtime subscriptions * [Quickstart](/docs/builders/shreds/quickstart) - Build your first app # Shreds (/docs/builders/shreds) import { Card, Cards } from 'fumadocs-ui/components/card'; import { Zap, Code, Radio } from 'lucide-react'; import { ShredsDemo } from '@/components/shreds/ShredsDemo'; import { ComponentPreviewTabs } from '@/components/rise-wallet/ComponentPreviewTabs'; import { CODE_EXAMPLES } from '@/components/rise-wallet/code-examples'; # Shreds Shreds are RISE's breakthrough innovation for realtime blockchain transactions. Rather than processing transactions in large batches (blocks), RISE processes them individually and immediately, providing confirmations in as little as **3 milliseconds**. ## Overview Traditional blockchains batch transactions into blocks, forcing users to wait seconds or minutes for confirmation. Shreds solve this by propagating incremental state updates across the network in realtime. Each shred is a lightweight, cryptographically signed packet containing state changes from one or more transactions, enabling instant confirmations while maintaining full EVM compatibility and security guarantees. ## Interactive Demo Try incrementing the counter below. Each transaction confirms in milliseconds and events arrive in realtime via WebSocket. Performance Optimization: This demo uses local nonce management and hardcoded gas values to minimize RPC calls, reducing network round-trips from 3+ calls (chainId, nonce, gas estimation) to just 1 (transaction submission). For simpler implementations, you can use sendTransactionSync() directly - it's easier but makes multiple RPC calls. Viem will soon integrate this optimization natively. See the commented code at the bottom for the simpler approach. } > ## Why Shreds? * **3ms confirmation times**: Transactions confirm faster than a blink of an eye * **Full EVM compatibility**: Works with existing Ethereum tools and libraries * **Real-time updates**: Subscribe to state changes as they happen * **Maintained security**: Cryptographically signed, deterministic state transitions ## Get Started } title="Quickstart" href="/docs/builders/shreds/quickstart" description="Build your first realtime app in 15 minutes" /> } title="API Methods" href="/docs/builders/shreds/api-methods" description="Learn the core Shred API methods" /> } title="Watching Events" href="/docs/builders/shreds/watching-events" description="Realtime shred subscriptions and event streaming" /> # Quickstart (/docs/builders/shreds/quickstart) import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; import { Steps, Step } from 'fumadocs-ui/components/steps'; # Quickstart Build a realtime payment application using RISE's Shred API in under 15 minutes. This tutorial will demonstrate instant transaction confirmations and realtime balance updates. ## What We'll Build A simple payment application that: * Sends instant payments with immediate confirmation * Displays realtime balance updates * Shows live transaction history * Demonstrates shred subscriptions ## Prerequisites * Node.js 16+ installed * Basic TypeScript/JavaScript knowledge * A code editor (VS Code recommended) ## Project Setup ### Create a New Project ```bash mkdir rise-quickstart cd rise-quickstart npm init -y ``` ### Install Dependencies ```bash npm install shreds viem dotenv npm install -D typescript tsx @types/node ``` ```bash pnpm add shreds viem dotenv pnpm add -D typescript tsx @types/node ``` ```bash yarn add shreds viem dotenv yarn add -D typescript tsx @types/node ``` ```bash bun add shreds viem dotenv bun add -D typescript tsx @types/node ``` ### Configure TypeScript Create `tsconfig.json`: ```json title="tsconfig.json" { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "resolveJsonModule": true } } ``` ### Set Up Environment Create `.env`: ```bash title=".env" # RISE Testnet RPC endpoints RISE_RPC_URL=https://testnet.riselabs.xyz RISE_WS_URL=wss://testnet.riselabs.xyz/ws # Test wallet (has testnet tokens) PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 ``` This is a well-known test private key. Never use it for real funds! ## Build the Application Create `quickstart.ts`: ```typescript title="quickstart.ts" import 'dotenv/config' import { createWalletClient, createPublicClient, http, webSocket, parseEther, formatEther } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { riseTestnet } from 'viem/chains' import { shredActions } from 'shreds/viem' // Setup account from private key const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) console.log('Wallet address:', account.address) // Create client and extend with shred actions const client = createWalletClient({ account, chain: riseTestnet, transport: http(process.env.RISE_RPC_URL) }).extend(shredActions) // Recipient address const recipient = '0x70997970C51812dc3A010C7d01b50e0d17dc79C8' async function main() { console.log('[START] RISE Quickstart\n') // Step 1: Check initial balances console.log('[INFO] Checking initial balances...') const senderBalance = await client.getBalance({ address: account.address }) const recipientBalance = await client.getBalance({ address: recipient }) console.log(`Sender: ${formatEther(senderBalance)} ETH`) console.log(`Recipient: ${formatEther(recipientBalance)} ETH\n`) // Step 2: Subscribe to shreds for realtime updates console.log('[SUBSCRIBE] Subscribing to realtime updates...\n') const wsClient = createPublicClient({ chain: riseTestnet, transport: webSocket(process.env.RISE_WS_URL) }).extend(shredActions) const unwatch = wsClient.watchShreds({ onShred: (shred) => { console.log(`[SHRED] New shred detected!`) console.log(` Index: ${shred.shredIndex}`) console.log(` Transactions: ${shred.transactions.length}`) console.log(` Timestamp: ${new Date(Number(shred.blockTimestamp) * 1000).toLocaleTimeString()}\n`) } }) // Step 3: Send instant transaction console.log('[SEND] Sending 0.1 ETH with instant confirmation...') const startTime = Date.now() try { const hash = await client.sendTransactionSync({ to: recipient, value: parseEther('0.1'), }) const confirmTime = Date.now() - startTime console.log(`[SUCCESS] Transaction confirmed in ${confirmTime}ms!`) console.log(`[HASH] ${hash}\n`) // Step 4: Check updated balances immediately console.log('[INFO] Checking updated balances...') const newSenderBalance = await client.getBalance({ address: account.address }) const newRecipientBalance = await client.getBalance({ address: recipient }) console.log(`Sender: ${formatEther(newSenderBalance)} ETH`) console.log(`Recipient: ${formatEther(newRecipientBalance)} ETH`) } catch (error) { console.error('[ERROR] Transaction failed:', error.message) } // Keep watching for a few seconds console.log('\n[WATCH] Watching for more shreds (10 seconds)...') setTimeout(() => { unwatch() console.log('\n[COMPLETE] Quickstart complete!') process.exit(0) }, 10000) } // Run the quickstart main().catch(console.error) ``` ## Run the Application Execute the quickstart: ```bash npx tsx quickstart.ts ``` You should see output like: ``` [START] RISE Quickstart [INFO] Checking initial balances... Sender: 10000.0 ETH Recipient: 10000.0 ETH [SUBSCRIBE] Subscribing to realtime updates... [SEND] Sending 0.1 ETH with instant confirmation... [SHRED] New shred detected! Index: 1234 Transactions: 1 Timestamp: 2:34:56 PM [SUCCESS] Transaction confirmed in 4ms! [HASH] 0x... [INFO] Checking updated balances... Sender: 9999.899... ETH Recipient: 10000.1 ETH [WATCH] Watching for more shreds (10 seconds)... ``` ## What Just Happened? 1. **Instant Confirmation**: The transaction confirmed in \~4ms, not minutes 2. **Realtime Updates**: Balances reflected the change immediately 3. **Shred Subscription**: We received the shred notification in realtime 4. **Full Compatibility**: Standard viem methods worked seamlessly ## Next Steps ### Enhance the Application Try these modifications: 1. **Batch Transactions**: Send multiple transactions rapidly 2. **Event Monitoring**: Watch for specific smart contract events 3. **Balance Streaming**: Display continuously updating balances 4. **Error Recovery**: Add retry logic for failed transactions ### Learn More * [API Methods](/docs/builders/shreds/api-methods) - Core Shred API methods * [Watching Events](/docs/builders/shreds/watching-events) - Realtime subscriptions # Watching Events (/docs/builders/shreds/watching-events) # Watching Events RISE enhances standard Ethereum subscriptions with realtime shred data, delivering events in milliseconds instead of seconds. ## Overview WebSocket subscriptions on RISE work just like standard Ethereum, but with dramatically faster event delivery. Events are streamed from shreds as transactions are processed, providing instant notifications for your applications. ## Shred Subscription Subscribe to new shreds in realtime for instant transaction visibility. ### Subscribe ```json { "jsonrpc": "2.0", "method": "eth_subscribe", "params": ["shreds"], "id": 1 } ``` You can add an extra `true` parameter to populate the `stateChanges` field in shred notifications: ```json { "jsonrpc": "2.0", "method": "eth_subscribe", "params": ["shreds", true], "id": 1 } ``` Without this parameter, `stateChanges` will be an empty array by default. ### Subscription Response ```json { "jsonrpc": "2.0", "result": "0x9ce59a13059e417087c02d3236a0b1cc", "id": 1 } ``` ### Notification Format ```json { "jsonrpc": "2.0", "method": "eth_subscription", "params": { "subscription": "0x9ce59a13059e417087c02d3236a0b1cc", "result": { "blockTimestamp": "0x...", "blockNumber": "0x1", "shredIndex": 0, "startingLogIndex": 0, "transactions": [ { "hash": "0x...", "status": "success", "gasUsed": "0x5208", "cumulativeGasUsed": "0x5208", "logs": [] } ], "stateChanges": [ { "address": "0x...", "nonce": 1, "balance": "0x...", "storageChanges": [], "newCode": null } ] } } } ``` ### Shred Object Structure ```typescript interface Shred { blockTimestamp: bigint; blockNumber: bigint; shredIndex: number; startingLogIndex: number; transactions: ShredTransaction[]; stateChanges: ShredStateChange[]; } interface ShredTransaction { hash: string; from: string; to: string; value: bigint; gas: bigint; status: "success" | "reverted"; type: "eip1559" | "legacy" | "eip2930" | "eip7702" | "deposit"; cumulativeGasUsed: bigint; logs: Array<{ address: string; topics: string[]; data: string; }>; // Additional fields available depending on transaction type gasPrice?: bigint; // legacy, eip2930 maxFeePerGas?: bigint; // eip1559, eip7702 maxPriorityFeePerGas?: bigint; // eip1559, eip7702 accessList?: AccessList; // eip2930, eip1559, eip7702 chainId?: number; nonce?: bigint; } interface ShredStateChange { address: string; nonce: number; balance: string; storageChanges: StorageChange[]; newCode: string | null; } ``` ### Using with viem ```typescript import { createPublicClient, webSocket } from 'viem' import { shredActions } from 'shreds/viem' import { riseTestnet } from 'viem/chains' const client = createPublicClient({ chain: riseTestnet, transport: webSocket('wss://testnet.riselabs.xyz/ws') }).extend(shredActions) // Watch for new shreds const unwatch = client.watchShreds({ includeStateChanges: true, // Optional: include state changes in shreds onShred: (shred) => { console.log(`Shred ${shred.shredIndex} confirmed`) console.log(`Transactions: ${shred.transactions.length}`) shred.transactions.forEach(tx => { console.log(`- ${tx.hash}: ${tx.status}`) }) }, onError: (error) => { console.error('Subscription error:', error) } }) // Stop watching // unwatch() ``` ## Logs Subscription Subscribe to contract event logs with shred-speed delivery. On RISE, logs are delivered from shreds instead of blocks for faster event processing. ### Subscribe with Filter ```json { "jsonrpc": "2.0", "method": "eth_subscribe", "params": [ "logs", { "address": "0x...", "topics": ["0x..."] } ], "id": 1 } ``` ### Enhanced Behavior * Events delivered immediately when transactions are processed in shreds * No waiting for block confirmation * Standard Ethereum log format maintained for compatibility * Filters work exactly like standard Ethereum ### Using with viem ```typescript // Watch for all Transfer events on a token contract const unwatch = client.watchContractEvent({ address: '0x...', abi: erc20Abi, eventName: 'Transfer', onLogs: logs => { // Events arrive in milliseconds! logs.forEach(log => { console.log(`Transfer: ${log.args.from} -> ${log.args.to}`) console.log(`Amount: ${formatEther(log.args.value)}`) }) } }) ``` ### Filter Specific Events ```typescript // Watch for transfers to a specific address client.watchContractEvent({ address: tokenAddress, abi: erc20Abi, eventName: 'Transfer', args: { to: myAddress // Filter by recipient }, onLogs: logs => { console.log(`Received ${logs.length} transfers`) } }) ``` ## Example ### Balance Monitor Monitor an address for balance changes in realtime: ```typescript let lastBalance = await client.getBalance({ address: monitoredAddress }) client.watchShreds({ onShred: async (shred) => { // Check if this shred affects the monitored address const affected = shred.stateChanges.find( change => change.address.toLowerCase() === monitoredAddress.toLowerCase() ) if (affected) { const newBalance = BigInt(affected.balance) const change = newBalance - lastBalance console.log(`Balance changed by ${formatEther(change)} ETH`) console.log(`New balance: ${formatEther(newBalance)} ETH`) lastBalance = newBalance } } }) ``` ## Multiple Subscriptions You can maintain multiple subscriptions simultaneously: ```typescript // Watch shreds const unwatchShreds = client.watchShreds({ onShred: handleShred }) // Watch token transfers const unwatchTransfers = client.watchContractEvent({ address: tokenAddress, abi: erc20Abi, eventName: 'Transfer', onLogs: handleTransfers }) // Watch DEX swaps const unwatchSwaps = client.watchContractEvent({ address: dexAddress, abi: dexAbi, eventName: 'Swap', onLogs: handleSwaps }) // Clean up when done // unwatchShreds() // unwatchTransfers() // unwatchSwaps() ``` ## Error Handling Handle connection errors and reconnection: ```typescript client.watchShreds({ onShred: (shred) => { console.log('New shred:', shred.shredIndex) }, onError: (error) => { console.error('Subscription error:', error) // Implement reconnection logic } }) ``` ## Performance Characteristics ### Event Delivery Times * **Shred Events**: 3-5ms from transaction execution * **Log Events**: 3-5ms from transaction execution * **State Changes**: Immediate in shred notification ### Throughput * **Events per Second**: 10,000+ * **Concurrent Subscriptions**: Unlimited * **Message Size**: Optimized for low latency ## Best Practices ### 1. Use Event Filters Filter events at the RPC level to reduce bandwidth: ```typescript // ✅ Good - filtered at source client.watchContractEvent({ address: tokenAddress, eventName: 'Transfer', args: { to: myAddress } }) // ❌ Avoid - filtering client-side client.watchContractEvent({ address: tokenAddress, eventName: 'Transfer', onLogs: logs => { const filtered = logs.filter(log => log.args.to === myAddress) } }) ``` ### 2. Handle High-Frequency Events Debounce or batch updates for high-frequency events: ```typescript let pendingUpdates = [] client.watchContractEvent({ eventName: 'PriceUpdate', onLogs: logs => { pendingUpdates.push(...logs) } }) // Batch process every 100ms setInterval(() => { if (pendingUpdates.length > 0) { processBatch(pendingUpdates) pendingUpdates = [] } }, 100) ``` ### 3. Clean Up Subscriptions Always unsubscribe when done: ```typescript const unwatch = client.watchShreds({ onShred }) // In cleanup (e.g., React useEffect) return () => { unwatch() } ``` ## Next Steps * [API Methods](/docs/builders/shreds/api-methods) - Core Shred API methods * [Quickstart](/docs/builders/shreds/quickstart) - Build your first app # Fast VRF (/docs/builders/vrf) import { Card, Cards } from 'fumadocs-ui/components/card'; import { Zap, Code, Radio } from 'lucide-react'; import { DiceGameWidget } from '@/components/vrf/DiceGameWidget'; import { ComponentPreviewTabs } from '@/components/rise-wallet/ComponentPreviewTabs'; import { CODE_EXAMPLES } from '@/components/rise-wallet/code-examples'; # Fast VRF Fast VRF enables verifiable random number generation for smart contracts with realtime delivery through RISE's shred architecture. Results arrive in **3-5ms** via WebSocket, enabling responsive gaming experiences. ## Overview Fast VRF provides cryptographically secure randomness for smart contracts with millisecond-level response times. Traditional VRF solutions require multiple block confirmations, taking seconds or minutes. RISE's shred architecture delivers VRF results instantly through WebSocket subscriptions, enabling truly responsive gaming and DeFi applications with standard Solidity interfaces. ## Interactive Demo Try the dice game below. It uses RISE Wallet to request a random number, which is fulfilled instantly by the VRF Coordinator. ## Key Features * **3-5ms response times**: VRF results delivered faster than a blink * **Real-time updates**: WebSocket subscriptions for instant notifications * **Full EVM compatibility**: Standard Solidity interfaces work with existing tooling * **Cryptographically secure**: Verifiable randomness you can trust ## Common Use Cases * Gaming: dice rolls, card shuffling, loot generation * NFT minting with random trait generation * DeFi lottery and reward distribution * DAO jury selection and voting mechanisms ## VRF Coordinator | Network | Address | | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **RISE Testnet** | [`0x9d57aB4517ba97349551C876a01a7580B1338909`](https://explorer.testnet.riselabs.xyz/address/0x9d57aB4517ba97349551C876a01a7580B1338909) | ## Get Started } title="Quickstart" href="/docs/builders/vrf/quickstart" description="Build a dice game in 15 minutes" /> } title="Smart Contracts" href="/docs/builders/vrf/smart-contracts" description="Integration guide and patterns" /> } title="Real-time Tracking" href="/docs/builders/vrf/real-time" description="WebSocket event subscriptions" /> # Quickstart (/docs/builders/vrf/quickstart) import { Steps, Step } from 'fumadocs-ui/components/steps'; import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; # VRF Quickstart Build a dice game with verifiable randomness and real-time updates in under 15 minutes. This tutorial demonstrates VRF integration with instant result notifications. ## What We'll Build A dice game that: * Requests verifiable random numbers * Tracks player streaks for rolling sixes * Updates UI in real-time via WebSocket * Handles VRF callbacks securely ## Prerequisites * Node.js 16+ installed * Basic Solidity knowledge * MetaMask or similar wallet * RISE testnet tokens ## Smart Contract Setup ### Create the Project ```bash mkdir vrf-dice-game cd vrf-dice-game forge init ``` ### Write the Dice Game Contract Create `src/DiceGame.sol`: ```solidity title="src/DiceGame.sol" // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IVRFCoordinator { function requestRandomNumbers(uint32 numNumbers, uint256 seed) external returns (uint256); } interface IVRFConsumer { function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external; } contract DiceGame is IVRFConsumer { IVRFCoordinator public coordinator; mapping(address => bool) public hasPendingRoll; mapping(address => uint256) public currentStreak; mapping(address => uint256) public topStreak; mapping(uint256 => address) public requestOwners; uint256 public requestCount = 0; uint8 private constant DICE_SIDES = 6; event DiceRollRequested(address indexed player, uint256 indexed requestId); event DiceRollCompleted( address indexed player, uint256 indexed requestId, uint256 result, uint256 currentStreak, uint256 topStreak ); event NewTopScore(address indexed player, uint256 newTopStreak); constructor(address _coordinator) { coordinator = IVRFCoordinator(_coordinator); } function rollDice() external returns (uint256 requestId) { require(!hasPendingRoll[msg.sender], "Already has a pending roll"); hasPendingRoll[msg.sender] = true; uint256 seed = requestCount++; requestId = coordinator.requestRandomNumbers(1, seed); requestOwners[requestId] = msg.sender; emit DiceRollRequested(msg.sender, requestId); return requestId; } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external { require(msg.sender == address(coordinator), "Only coordinator can fulfill"); require(randomNumbers.length > 0, "No random numbers provided"); address player = requestOwners[requestId]; require(player != address(0), "Unknown request ID"); require(hasPendingRoll[player], "No pending roll"); uint256 diceRoll = (randomNumbers[0] % DICE_SIDES) + 1; if (diceRoll == 6) { currentStreak[player] += 1; if (currentStreak[player] > topStreak[player]) { topStreak[player] = currentStreak[player]; emit NewTopScore(player, topStreak[player]); } } else { currentStreak[player] = 0; } hasPendingRoll[player] = false; emit DiceRollCompleted( player, requestId, diceRoll, currentStreak[player], topStreak[player] ); delete requestOwners[requestId]; } } ``` ### Deploy to RISE Testnet Configure `foundry.toml`: ```toml title="foundry.toml" [profile.default] src = "src" out = "out" libs = ["lib"] solc_version = "0.8.19" [rpc_endpoints] rise_testnet = "https://testnet.riselabs.xyz" [etherscan] rise_testnet = { key = "", url = "https://explorer.testnet.riselabs.xyz/api" } ``` Deploy: ```bash forge create --rpc-url rise_testnet \ --private-key $PRIVATE_KEY \ --constructor-args 0x9d57aB4517ba97349551C876a01a7580B1338909 \ src/DiceGame.sol:DiceGame ``` Verify on Blockscout: ```bash forge verify-contract \ src/DiceGame.sol:DiceGame \ --chain 11155931 \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api \ --constructor-args $(cast abi-encode "constructor(address)" 0x9d57aB4517ba97349551C876a01a7580B1338909) ``` ## Frontend Integration ### Setup Frontend ```bash npm install viem ``` ```bash pnpm add viem ``` ```bash yarn add viem ``` ```bash bun add viem ``` ### Monitor VRF Events Create `monitor.ts` to track dice roll events: ```typescript title="monitor.ts" import { createPublicClient, webSocket } from 'viem'; import { riseTestnet } from 'viem/chains'; const wsUrl = 'wss://testnet.riselabs.xyz/ws'; const diceGameAddress = '0x...'; // Your deployed contract const vrfAbi = [ { type: 'event', name: 'DiceRollCompleted', inputs: [ { name: 'player', type: 'address', indexed: true }, { name: 'requestId', type: 'uint256', indexed: true }, { name: 'result', type: 'uint256' }, { name: 'currentStreak', type: 'uint256' }, { name: 'topStreak', type: 'uint256' } ] } ] as const; const client = createPublicClient({ chain: riseTestnet, transport: webSocket(wsUrl) }); const unwatch = client.watchContractEvent({ address: diceGameAddress, abi: vrfAbi, eventName: 'DiceRollCompleted', onLogs: (logs) => { logs.forEach((log) => { console.log(`Player rolled: ${log.args.result}`); console.log(`Streak: ${log.args.currentStreak}`); }); } }); ``` ## How It Works 1. **User initiates**: Click roll button in the DApp 2. **Request randomness**: DApp calls `rollDice()` on the contract 3. **VRF processing**: Contract requests random numbers from VRF coordinator 4. **Instant callback**: VRF calls `rawFulfillRandomNumbers()` with result 5. **Event emission**: Contract emits `DiceRollCompleted` event 6. **Real-time update**: WebSocket delivers event to DApp in \~4ms 7. **UI update**: DApp shows the dice result immediately ## Key Takeaways * **Instant Results**: VRF responds in milliseconds via shreds * **Real-time Updates**: WebSocket delivers events instantly * **Secure Randomness**: Cryptographically verifiable numbers * **Simple Integration**: Standard Solidity interfaces ## Next Steps * Add betting mechanics with token stakes * Create multiplayer dice battles * Implement leaderboards * Add different game modes # Realtime Tracking (/docs/builders/vrf/real-time) import { Card } from 'fumadocs-ui/components/card'; import { Radio } from 'lucide-react'; # Realtime Event Tracking Get instant notifications when VRF results are ready using WebSocket subscriptions powered by RISE's shred architecture. ## Overview VRF results arrive via events emitted by your contract. RISE Chain's shred-based architecture delivers these events in realtime through WebSocket subscriptions, eliminating the need for polling and providing instant updates when randomness is ready. } title="Watching Events" href="/docs/builders/shreds/watching-events"> For complete details on realtime event tracking with WebSockets and shred subscriptions, see the Watching Events guide. The same patterns apply to VRF events. ## Quick Example Here's how to watch for VRF completion events: ```typescript import { createPublicClient, webSocket } from 'viem'; import { riseTestnet } from 'viem/chains'; const client = createPublicClient({ chain: riseTestnet, transport: webSocket('wss://testnet.riselabs.xyz/ws') }); const vrfAbi = [ { type: 'event', name: 'DiceRollCompleted', inputs: [ { name: 'player', type: 'address', indexed: true }, { name: 'requestId', type: 'uint256', indexed: true }, { name: 'result', type: 'uint256' }, { name: 'currentStreak', type: 'uint256' }, { name: 'topStreak', type: 'uint256' } ] } ] as const; // Watch for VRF completion events client.watchContractEvent({ abi: vrfAbi, address: '0x...', // Your VRF consumer contract eventName: 'DiceRollCompleted', onLogs: (logs) => { logs.forEach((log) => { console.log('Random result:', log.args.result); console.log('Streak:', log.args.currentStreak); // Update your UI here updateUI(log.args.result); }); } }); ``` ## Key Differences for VRF When watching VRF events, you'll typically want to track: 1. **Request Events**: Know when a randomness request is made 2. **Completion Events**: Get notified instantly when VRF fulfills the request (usually 3-5ms) 3. **Request IDs**: Match requests to their results The event watching patterns are identical to those used for shreds. For advanced patterns like multi-contract monitoring, React hooks, reconnection handling, and more, refer to the Watching Events guide linked above. # Smart Contracts (/docs/builders/vrf/smart-contracts) # Smart Contract Integration Learn how to integrate Fast VRF into your smart contracts for secure, verifiable randomness. ## VRF Coordinator The VRF Coordinator manages randomness requests and fulfillment on RISE Chain. ```solidity // RISE Testnet VRF Coordinator address constant VRF_COORDINATOR = 0x9d57aB4517ba97349551C876a01a7580B1338909; ``` ## Required Interfaces Your contract must implement these interfaces to interact with VRF: ```solidity // Interface for requesting random numbers interface IVRFCoordinator { function requestRandomNumbers( uint32 numNumbers, // How many random numbers you need uint256 seed // Seed for randomness generation ) external returns (uint256 requestId); } // Interface your contract must implement interface IVRFConsumer { function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external; } ``` ## Basic Implementation ### Minimal VRF Consumer ```solidity contract BasicVRFConsumer is IVRFConsumer { IVRFCoordinator public coordinator; mapping(uint256 => uint256) public results; event RandomnessRequested(uint256 indexed requestId); event RandomnessFulfilled(uint256 indexed requestId, uint256 randomNumber); constructor(address _coordinator) { coordinator = IVRFCoordinator(_coordinator); } function requestRandom() external returns (uint256) { uint256 requestId = coordinator.requestRandomNumbers(1, block.timestamp); emit RandomnessRequested(requestId); return requestId; } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override { require(msg.sender == address(coordinator), "Only VRF coordinator"); require(randomNumbers.length > 0, "No random numbers"); results[requestId] = randomNumbers[0]; emit RandomnessFulfilled(requestId, randomNumbers[0]); } } ``` ## Advanced Patterns ### Request Tracking Track who made each request and handle state properly: ```solidity contract TrackedVRFConsumer is IVRFConsumer { IVRFCoordinator public coordinator; struct Request { address requester; uint256 timestamp; bool fulfilled; uint256 result; } mapping(uint256 => Request) public requests; mapping(address => uint256[]) public userRequests; function requestRandomForUser() external returns (uint256) { uint256 requestId = coordinator.requestRandomNumbers( 1, uint256(keccak256(abi.encode(msg.sender, block.timestamp))) ); requests[requestId] = Request({ requester: msg.sender, timestamp: block.timestamp, fulfilled: false, result: 0 }); userRequests[msg.sender].push(requestId); return requestId; } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override { require(msg.sender == address(coordinator), "Only VRF coordinator"); Request storage request = requests[requestId]; require(request.requester != address(0), "Invalid request"); require(!request.fulfilled, "Already fulfilled"); request.fulfilled = true; request.result = randomNumbers[0]; // Process the result for the user _processRandomness(request.requester, randomNumbers[0]); } function _processRandomness(address user, uint256 randomNumber) internal { // Your custom logic here } } ``` ### Multiple Random Numbers Request and handle multiple random values in one call: ```solidity contract MultiRandomConsumer is IVRFConsumer { IVRFCoordinator public coordinator; event LotteryDrawn(uint256[] winningNumbers); function drawLottery() external returns (uint256) { // Request 6 random numbers for lottery return coordinator.requestRandomNumbers(6, block.timestamp); } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override { require(msg.sender == address(coordinator), "Only VRF coordinator"); require(randomNumbers.length == 6, "Expected 6 numbers"); uint256[] memory lotteryNumbers = new uint256[](6); for (uint i = 0; i < 6; i++) { // Generate lottery numbers 1-49 lotteryNumbers[i] = (randomNumbers[i] % 49) + 1; } emit LotteryDrawn(lotteryNumbers); } } ``` ## Common Use Cases ### Fair NFT Minting ```solidity contract FairNFTMint is IVRFConsumer, ERC721 { IVRFCoordinator public coordinator; uint256 public constant MAX_SUPPLY = 10000; uint256 public totalMinted = 0; mapping(uint256 => address) public mintRequests; mapping(uint256 => uint256) public tokenTraits; function requestMint() external payable { require(msg.value >= 0.1 ether, "Insufficient payment"); require(totalMinted < MAX_SUPPLY, "Sold out"); uint256 requestId = coordinator.requestRandomNumbers(1, totalMinted); mintRequests[requestId] = msg.sender; } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override { require(msg.sender == address(coordinator), "Only VRF coordinator"); address minter = mintRequests[requestId]; require(minter != address(0), "Invalid request"); uint256 tokenId = totalMinted++; uint256 traits = randomNumbers[0]; tokenTraits[tokenId] = traits; _mint(minter, tokenId); delete mintRequests[requestId]; } } ``` ### Random Rewards Distribution ```solidity contract RandomRewards is IVRFConsumer { IVRFCoordinator public coordinator; IERC20 public rewardToken; address[] public participants; mapping(uint256 => uint256) public pendingRewards; function distributeRewards(uint256 totalReward) external { require(participants.length > 0, "No participants"); uint256 requestId = coordinator.requestRandomNumbers( uint32(participants.length), block.timestamp ); pendingRewards[requestId] = totalReward; } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override { require(msg.sender == address(coordinator), "Only VRF coordinator"); uint256 totalReward = pendingRewards[requestId]; require(totalReward > 0, "No pending reward"); // Distribute proportionally based on random weights uint256 totalWeight = 0; for (uint i = 0; i < randomNumbers.length; i++) { totalWeight += randomNumbers[i] % 1000; } for (uint i = 0; i < participants.length; i++) { uint256 share = (totalReward * (randomNumbers[i] % 1000)) / totalWeight; rewardToken.transfer(participants[i], share); } delete pendingRewards[requestId]; } } ``` ## Security Considerations ### Access Control Always verify the caller is the VRF coordinator: ```solidity modifier onlyVRFCoordinator() { require(msg.sender == address(coordinator), "Only VRF coordinator"); _; } function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override onlyVRFCoordinator { // Your logic here } ``` ### Request Validation Validate requests before processing: ```solidity function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override onlyVRFCoordinator { // Check request exists require(requests[requestId].requester != address(0), "Unknown request"); // Check not already fulfilled require(!requests[requestId].fulfilled, "Already fulfilled"); // Check expected number count require(randomNumbers.length == expectedCount[requestId], "Wrong count"); // Process... } ``` ### Reentrancy Protection Use checks-effects-interactions pattern: ```solidity function rawFulfillRandomNumbers( uint256 requestId, uint256[] memory randomNumbers ) external override onlyVRFCoordinator nonReentrant { // 1. Checks require(requests[requestId].valid, "Invalid request"); // 2. Effects requests[requestId].fulfilled = true; requests[requestId].result = randomNumbers[0]; // 3. Interactions if (callbacks[requestId] != address(0)) { ICallback(callbacks[requestId]).onRandomness(randomNumbers[0]); } } ``` ## Best Practices 1. **Always validate** the VRF coordinator address 2. **Track request state** to prevent double processing 3. **Handle failures gracefully** with fallback mechanisms 4. **Use events** for off-chain monitoring 5. **Test thoroughly** with mock VRF before mainnet 6. **Consider gas costs** when requesting multiple numbers 7. **Implement timeouts** for unfulfilled requests # Creating Permit Params (/docs/risex/api/creating-permit-params) # Creating Permit Params After registering a signer, you need to create permit params for each API call. Permit params include a signature from your signer that authorizes the transaction. ## Step 1: Encode Contract Data First, encode the contract data according to your operation. There are **3 different encoding methods** depending on the API endpoint: ### Method 1: Place Order Encoding (using `encodePacked`) For **place order** operations, use packed encoding with a specific binary layout (47 bytes total): ```typescript import { encodePacked, type Hex } from 'viem'; // Enum values enum OrderSide { Long = 0, Short = 1, } enum STPMode { ExpireMaker = 0, ExpireTaker = 1, ExpireBoth = 2, None = 3, } enum OrderType { Market = 0, Limit = 1, } enum TimeInForce { GoodTillCancelled = 0, GoodTillTime = 1, FillOrKill = 2, ImmediateOrCancel = 3, } interface EncodePlaceOrderParams { marketId: string; size: bigint; // uint128 price: bigint; // uint128 side: OrderSide; // 0 = Long/Buy, 1 = Short/Sell stpMode: STPMode; // 0-3 orderType: OrderType; // 0 = Market, 1 = Limit postOnly: boolean; reduceOnly: boolean; timeInForce?: TimeInForce; // Default: GoodTillCancelled (0) expiry: number; // uint32, Unix timestamp } const encodePlaceOrderData = ({ marketId, size, price, side, stpMode, orderType, postOnly, reduceOnly, timeInForce = TimeInForce.GoodTillCancelled, expiry, }: EncodePlaceOrderParams): Hex => { // Pack flags into a single uint8 byte: // bit 0: side (0 = Long/Buy, 1 = Short/Sell) // bit 1: postOnly // bit 2: reduceOnly // bits 3-4: stpMode (2 bits) // bits 5-7: unused let flags = 0; if (side === OrderSide.Short) flags |= 0x01; // bit 0 if (postOnly) flags |= 0x02; // bit 1 if (reduceOnly) flags |= 0x04; // bit 2 flags |= stpMode << 3; // bits 3-4 // Binary layout (47 bytes total): // bytes[0:8] - marketId (uint64, 8 bytes) // bytes[8:24] - size (uint128, 16 bytes) // bytes[24:40] - price (uint128, 16 bytes) // bytes[40] - flags (uint8, 1 byte) // bytes[41] - orderType (uint8, 1 byte) // bytes[42] - timeInForce (uint8, 1 byte) // bytes[43:47] - expiry (uint32, 4 bytes) return encodePacked( ['uint64', 'uint128', 'uint128', 'uint8', 'uint8', 'uint8', 'uint32'], [BigInt(marketId), size, price, flags, orderType, timeInForce, expiry], ); }; // Example usage: const encodedData = encodePlaceOrderData({ marketId: '1', size: BigInt('1000000000000000000'), // 1 token with 18 decimals price: BigInt('2000000000000000000000'), // 2000 with 18 decimals side: OrderSide.Long, stpMode: STPMode.None, orderType: OrderType.Limit, postOnly: false, reduceOnly: false, timeInForce: TimeInForce.GoodTillCancelled, expiry: Math.floor(Date.now() / 1000) + 86400, // 24 hours from now }); ``` ### Method 2: Cancel Order Encoding (using `encodePacked` with manual concatenation) For **cancel order** operations, use packed encoding with manual concatenation for uint192 (since viem doesn't support uint192 directly): ```typescript import { encodePacked, type Hex } from 'viem'; interface EncodeCancelOrderParams { marketId: string; // uint64 orderId: bigint; // uint192 (24 bytes) } const encodeCancelOrderData = ({ marketId, orderId }: EncodeCancelOrderParams): Hex => { // Binary layout (32 bytes total): // bytes[0:8] - marketId (uint64, 8 bytes) // bytes[8:32] - orderId (uint192, 24 bytes) // Encode marketId as uint64 const marketIdBytes = encodePacked(['uint64'], [BigInt(marketId)]); // Convert orderId to 24-byte hex string (uint192) // 24 bytes = 48 hex characters const orderIdHex = orderId.toString(16).padStart(48, '0'); // Concatenate: marketId (8 bytes) + orderId (24 bytes) = 32 bytes // Remove 0x prefix from marketIdBytes and prepend 0x to the final result return `0x${marketIdBytes.slice(2)}${orderIdHex}` as Hex; }; // Example usage: const encodedData = encodeCancelOrderData({ marketId: '1', orderId: BigInt('123456789012345678901234567890123456789012345678'), }); ``` ### Method 3: Other APIs Encoding (using `encodeAbiParameters`) For **all other APIs** (update leverage, update margin mode, update isolated margin, etc.), use ABI encoding which matches Solidity's `abi.encode`: ```typescript import { encodeAbiParameters, type Hex } from 'viem'; // Example 1: Update Leverage interface EncodeUpdateLeverageParams { marketId: string; // uint256 leverage: bigint; // uint128 } const encodeUpdateLeverageData = ({ marketId, leverage }: EncodeUpdateLeverageParams): Hex => { return encodeAbiParameters( [ { name: 'marketId', type: 'uint256' }, { name: 'leverage', type: 'uint128' }, ], [BigInt(marketId), leverage], ); }; // Example 2: Update Margin Mode interface EncodeUpdateMarginModeParams { marketId: string; // uint256 marginMode: number; // uint8 (0 = Cross, 1 = Isolated) } const encodeUpdateMarginModeData = ({ marketId, marginMode }: EncodeUpdateMarginModeParams): Hex => { return encodeAbiParameters( [ { name: 'marketId', type: 'uint256' }, { name: 'marginMode', type: 'uint8' }, ], [BigInt(marketId), marginMode], ); }; // Example 3: Update Isolated Position Margin Balance interface EncodeUpdateIsolatedPositionMarginBalanceParams { marketId: string; // uint256 amount: bigint; // int256 (positive for add, negative for remove) } const encodeUpdateIsolatedPositionMarginBalanceData = ({ marketId, amount, }: EncodeUpdateIsolatedPositionMarginBalanceParams): Hex => { return encodeAbiParameters( [ { name: 'marketId', type: 'uint256' }, { name: 'amount', type: 'int256' }, ], [BigInt(marketId), amount], ); }; // Example usage: const leverageEncoded = encodeUpdateLeverageData({ marketId: '1', leverage: BigInt('10'), // 10x leverage (plain value, no 18 decimals) }); const marginModeEncoded = encodeUpdateMarginModeData({ marketId: '1', marginMode: 1, // Isolated margin mode }); const isolatedMarginEncoded = encodeUpdateIsolatedPositionMarginBalanceData({ marketId: '1', amount: BigInt('1000000000000000000'), // Add 1 token (positive) // amount: BigInt('-1000000000000000000'), // Remove 1 token (negative) }); ``` ### Summary * **Place Order**: Use `encodePacked` with specific 47-byte layout * **Cancel Order**: Use `encodePacked` with manual concatenation for uint192 * **All Other APIs**: Use `encodeAbiParameters` matching Solidity's `abi.encode(params)` ## Step 2: Sign Encoded Data and Create Permit Params ```typescript const createPermitParams = async ( encodedData: Hex, signingKey: `0x${string}`, // The signing key from registerSigner account: `0x${string}`, signerAddress: `0x${string}`, // The signer address from registerSigner perpContractAddress: `0x${string}`, authContractAddress: `0x${string}`, ) => { const signerAccount = privateKeyToAccount(signingKey); // Hash the encoded data before signing const messageHash = keccak256(encodedData); // Create nonce const nonce = createClientNonce(account); // Set deadline (typically 7 days from now, in seconds) const deadline = dayjs().add(7, 'day').unix(); // Sign the hash with signer's private key using EIP-712 const domain = getRISExDomain(authContractAddress); const signature = await signerAccount.signTypedData({ domain: domain as any, types: { VerifySignature: VERIFY_SIGNATURE_TYPES.VerifySignature }, message: { account, target: perpContractAddress, hash: messageHash, nonce: BigInt(nonce), deadline: BigInt(deadline), }, primaryType: 'VerifySignature', }); return { account: account, signer: signerAddress, deadline: deadline.toString(), signature: signature, nonce: nonce, }; }; ``` ## Step 3: Use Permit Params in API Calls ```typescript // Example: Place order with permit params const placeOrderWithPermit = async ( orderParams: { market_id: string; size: string; price: string; side: string; stp_mode: string; order_type: string; post_only: boolean; reduce_only: boolean; tif: string; expiry: number; }, signingKey: `0x${string}`, signerAddress: `0x${string}`, account: `0x${string}`, ) => { try { // Step 1: Get contract addresses from system config or check "Contract Address" section const configResponse = await apiClient.get('/v1/system/config'); const perpContractAddress = configResponse.data.addresses_config.perp; const authContractAddress = configResponse.data.addresses_config.auth; // Step 2: Encode order data (implementation depends on your contract ABI) // const encodedData = encodePlaceOrderData(orderParams); // Step 3: Create permit params // const permitParams = await createPermitParams( // encodedData, // signingKey, // account, // signerAddress, // perpContractAddress, // authContractAddress, // ); // Step 4: Make API call with permit params const response = await apiClient.post('/v1/orders/place', { order_params: orderParams, permit_params: { account: account, signer: signerAddress, deadline: dayjs().add(7, 'day').unix().toString(), signature: '0x...', // From createPermitParams nonce: '1234567890', // From createPermitParams }, }); return response.data; } catch (error) { if (axios.isAxiosError(error)) { console.error('Order failed:', error.response?.data?.message || error.message); } else { console.error('Order failed:', error); } throw error; } }; ``` ## Important Notes * **Required for all authenticated calls**: Every API call that modifies state needs permit params * **Unique nonce**: Each permit param must have a unique nonce (use `createClientNonce`) * **Deadline**: Typically set to 7 days from now * **Signature**: Must sign the encoded contract data hash with the signer's private key # Important Notes (/docs/risex/api/important-notes) # Important Notes ## Signer Registration * **One-time setup**: Register signer once per account * **Store securely**: Keep the `signingKey` private and secure - it authorizes all transactions * **Expiration**: Signers expire after the expiration time (typically 7 days) * **Re-registration**: You may need to re-register if the signer expires ## Permit Params * **Required for all authenticated calls**: Every API call that modifies state needs permit params * **Unique nonce**: Each permit param must have a unique nonce (use `createClientNonce`) * **Deadline**: Typically set to 7 days from now * **Signature**: Must sign the encoded contract data hash with the signer's private key ## General * All amounts should be in wei format (18 decimals) for blockchain transactions * Timestamps should be Unix timestamps (seconds) * Signature generation follows EIP-712 standard * Nonce should be unique for each request * Deadline should be set appropriately (typically 7 days from now) ## Security Best Practices * Never expose your `signingKey` in client-side code or public repositories * Store signing keys securely (use environment variables or secure storage) * Rotate signing keys periodically * Monitor for unauthorized transactions # Overview (/docs/risex/api) # RISE API Usage Examples This document provides example usage of the RISE API using axios. For complete API documentation, please refer to the [Full API Documentation](https://developer.rise.trade/reference/general-information). ## Base URL ``` https://api.testnet.rise.trade ``` ## Setup ```typescript import axios from 'axios'; import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; import { keccak256, type Hex } from 'viem'; import { createClientNonce } from 'risex-core'; import { formatSignature } from 'blockchain-core'; import dayjs from 'dayjs'; const API_BASE_URL = 'https://api.testnet.rise.trade'; // Create axios instance const apiClient = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, timeout: 30000, }); // Create nonce function nowInNano() { const rand6Digits = Math.round(Math.random() * 1000000) .toString() .padStart(6, '0'); return `${dayjs().valueOf()}${rand6Digits}`; }; function hashToUint32(input: string) { let hash = 0; for (let i = 0; i < input.length; i += 1) { hash = (hash * 31 + input.charCodeAt(i)) | 0; } return hash >>> 0; } const createClientNonce = (address: string | undefined) => { const baseNonce = nowInNano(); const secondOfNonce = baseNonce.slice(0, -9); return baseNonce.slice(0, -6) + hashToUint32(`${secondOfNonce}${address?.toLowerCase()}`).toString().slice(-6); }; // Domain for EIP-712 signing const RISEx_CHAIN_ID = 11155931; const getRISExDomain = (authContractAddress: string) => ({ name: 'RISEx', version: '1', chainId: RISEx_CHAIN_ID, verifyingContract: authContractAddress, }); // EIP-712 Type definitions const REGISTER_TYPES = { EIP712Domain: [ { name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }, ], RegisterSigner: [ { name: 'signer', type: 'address' }, { name: 'message', type: 'string' }, { name: 'expiration', type: 'uint40' }, { name: 'nonce', type: 'uint256' }, ], VerifySigner: [ { name: 'account', type: 'address' }, { name: 'nonce', type: 'uint256' }, ], }; const VERIFY_SIGNATURE_TYPES = { VerifySignature: [ { name: 'account', type: 'address' }, { name: 'target', type: 'address' }, // perp or spot address { name: 'hash', type: 'bytes32' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, // seconds ], }; // Enum values enum OrderSide { Long = 0, Short = 1, } enum STPMode { ExpireMaker = 0, ExpireTaker = 1, ExpireBoth = 2, None = 3, } enum OrderType { Market = 0, Limit = 1, } enum TimeInForce { GoodTillCancelled = 0, GoodTillTime = 1, FillOrKill = 2, ImmediateOrCancel = 3, } enum MarketsId { BTC = 0, ETH = 1 } ``` ## Full API Documentation For complete API documentation including: * All request/response schemas * Authentication requirements * Rate limits * WebSocket endpoints * Error codes Please visit: [Full API Documentation](https://developer.rise.trade/reference/general-information) # Register Signer (/docs/risex/api/register-signer) # Authentication: Register Signer Before making any authenticated API calls, you need to register a signer. The signer is a private key that will be used to sign transactions on behalf of your account. ## Step 1: Generate Signer Key ```typescript // Generate a new private key for the signer const signingKey = generatePrivateKey(); const signerAccount = privateKeyToAccount(signingKey); const signerAddress = signerAccount.address; // This is your signer address ``` ## Step 2: Create Signer Signature ```typescript const createSignerSignature = async ( account: `0x${string}`, authContractAddress: `0x${string}`, ) => { // Generate a new private key for the signer const signingKey = generatePrivateKey(); const signerAccount = privateKeyToAccount(signingKey); const signerAddress = signerAccount.address; // Create nonce const nonce = createClientNonce(account); const domain = getRISExDomain(authContractAddress); // Sign with the signer's private key const signingKeySignature = await signerAccount.signTypedData({ domain, types: { VerifySigner: REGISTER_TYPES.VerifySigner }, message: { account, nonce, // Note: nonce is a string, not BigInt }, primaryType: 'VerifySigner', }); return { signerAddress, signingKeySignature, signingKey, // Store this securely for future use nonce, }; }; ``` ## Step 3: Create Account Signature (User Wallet Signature) ```typescript const createAccountSignature = async ( signerAddress: `0x${string}`, nonce: string, authContractAddress: `0x${string}`, // You'll need a wallet provider (like wagmi, ethers, etc.) signTypedDataAsync: (data: any) => Promise, ) => { const registerMessage = { signer: signerAddress, message: 'Please sign in with your wallet to access RISEx.', nonce, expiration: dayjs().add(7, 'day').unix(), // 7 days from now }; const domain = getRISExDomain(authContractAddress); // User signs with their wallet const accountSignature = await signTypedDataAsync({ types: REGISTER_TYPES, message: { signer: registerMessage.signer, message: registerMessage.message, nonce: BigInt(registerMessage.nonce), expiration: BigInt(registerMessage.expiration), }, primaryType: 'RegisterSigner', domain: domain as any, }); // Viem returns properly formatted signatures - no additional formatting needed return { accountSignature, registerMessage, }; }; ``` ## Step 4: Register Signer via API ```typescript const registerSigner = async ( account: `0x${string}`, authContractAddress: `0x${string}`, signTypedDataAsync: (data: any) => Promise, ) => { try { // Step 1: Generate signer and create signer signature const { signerAddress, signingKeySignature, signingKey, nonce } = await createSignerSignature(account, authContractAddress); // Step 2: Get user wallet signature const { accountSignature, registerMessage } = await createAccountSignature( signerAddress, nonce, authContractAddress, signTypedDataAsync, ); // Step 3: Register via API const response = await apiClient.post('/v1/auth/register-signer', { account: account, signer: registerMessage.signer, message: registerMessage.message, nonce: registerMessage.nonce, expiration: registerMessage.expiration.toString(), account_signature: accountSignature, // Note: uses account_signature, not signature signer_signature: signingKeySignature, // Note: uses signer_signature, not signature }); if (response.data) { console.log('Signer registered successfully:', { account, signer: signerAddress, signingKey, // Store this securely! expiration: registerMessage.expiration, }); // IMPORTANT: Store signingKey securely for future API calls return { signer: signerAddress, signingKey, // Keep this secret! }; } } catch (error) { if (axios.isAxiosError(error)) { console.error('Failed to register signer:', error.response?.data?.message || error.message); } else { console.error('Failed to register signer:', error); } throw error; } }; ``` ## Important Notes * **One-time setup**: Register signer once per account * **Store securely**: Keep the `signingKey` private and secure - it authorizes all transactions * **Expiration**: Signers expire after the expiration time (typically 7 days) * **Re-registration**: You may need to re-register if the signer expires # Deployments (/docs/risex/contracts/deployments) # Testnet Deployments Contract addresses for RISEx on RISE Testnet. | Contract | Address | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | PerpsManager | [`0x68cAcD54a8c93A3186BF50bE6b78B761F728E1b4`](https://explorer.testnet.riselabs.xyz/address/0x68cAcD54a8c93A3186BF50bE6b78B761F728E1b4) | | Auth | [`0x8d8708f9D87ef522c1f99DD579BF6A051e34C28E`](https://explorer.testnet.riselabs.xyz/address/0x8d8708f9D87ef522c1f99DD579BF6A051e34C28E) | | USDC | [`0x8d17fC7Db6b4FCf40AFB296354883DEC95a12f58`](https://explorer.testnet.riselabs.xyz/address/0x8d17fC7Db6b4FCf40AFB296354883DEC95a12f58) | | Oracle | [`0x0C7Be7DfAbBA609A5A215a716aDc4dF089EC3952`](https://explorer.testnet.riselabs.xyz/address/0x0C7Be7DfAbBA609A5A215a716aDc4dF089EC3952) | | Deposit | [`0x5BC20A936EfEE0d758A3c168d2f017c83805B986`](https://explorer.testnet.riselabs.xyz/address/0x5BC20A936EfEE0d758A3c168d2f017c83805B986) | | Multicall3 | [`0xca11bde05977b3631167028862be2a173976ca11`](https://explorer.testnet.riselabs.xyz/address/0xca11bde05977b3631167028862be2a173976ca11) | | Fee Manager | [`0xC96dF9c9CDc9A03A5c69BF291035f8299145c6EC`](https://explorer.testnet.riselabs.xyz/address/0xC96dF9c9CDc9A03A5c69BF291035f8299145c6EC) | # Get Started with Ethers.js (/docs/builders/frontend/ethers/get-started) Learn how to set up and configure Ethers.js for building on RISE. ## Installation Install Ethers.js v6 using your preferred package manager: ```bash npm install ethers ``` ## Project Setup Create a new project: ```bash mkdir my-rise-ethers-project cd my-rise-ethers-project npm init -y npm install ethers dotenv ``` ## Connect to RISE ### Using JsonRpcProvider Connect to RISE Testnet: ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); // Get network info const network = await provider.getNetwork(); console.log('Connected to:', network.name); console.log('Chain ID:', network.chainId); // Get latest block const blockNumber = await provider.getBlockNumber(); console.log('Latest block:', blockNumber); ``` ### Create a Wallet Create a wallet from a private key: ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); // From private key const wallet = new ethers.Wallet( 'YOUR_PRIVATE_KEY', provider ); console.log('Address:', wallet.address); // Get balance const balance = await provider.getBalance(wallet.address); console.log('Balance:', ethers.formatEther(balance), 'ETH'); ``` ### Generate New Wallet Create a new random wallet: ```javascript // Generate new wallet const newWallet = ethers.Wallet.createRandom(); console.log('Address:', newWallet.address); console.log('Private Key:', newWallet.privateKey); console.log('Mnemonic:', newWallet.mnemonic.phrase); // Connect to provider const connectedWallet = newWallet.connect(provider); ``` ### From Mnemonic Restore wallet from mnemonic phrase: ```javascript const mnemonic = 'your twelve word mnemonic phrase here ...'; const wallet = ethers.Wallet.fromPhrase(mnemonic, provider); console.log('Address:', wallet.address); ``` ## Environment Variables Create a `.env` file: ```bash PRIVATE_KEY=0x... RPC_URL=https://testnet.riselabs.xyz ``` Load and use environment variables: ```javascript import { ethers } from 'ethers'; import { config } from 'dotenv'; config(); const provider = new ethers.JsonRpcProvider(process.env.RPC_URL); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); console.log('Connected as:', wallet.address); ``` ## Network Configuration Create a config file for RISE network: ```javascript // config.js export const RISE_TESTNET = { chainId: 11155931, name: 'RISE Testnet', rpcUrl: 'https://testnet.riselabs.xyz', explorer: 'https://explorer.testnet.riselabs.xyz', symbol: 'ETH', decimals: 18 }; export function getProvider() { return new ethers.JsonRpcProvider(RISE_TESTNET.rpcUrl); } export function getWallet(privateKey) { const provider = getProvider(); return new ethers.Wallet(privateKey, provider); } ``` Usage: ```javascript import { getProvider, getWallet } from './config.js'; const provider = getProvider(); const wallet = getWallet(process.env.PRIVATE_KEY); ``` ## Basic Operations ### Check Account Balance ```javascript const address = '0x...'; const balance = await provider.getBalance(address); console.log('Balance (wei):', balance.toString()); console.log('Balance (ETH):', ethers.formatEther(balance)); ``` ### Get Transaction Count (Nonce) ```javascript const nonce = await provider.getTransactionCount(wallet.address); console.log('Transaction count:', nonce); ``` ### Get Gas Price ```javascript const feeData = await provider.getFeeData(); console.log('Gas Price:', ethers.formatUnits(feeData.gasPrice, 'gwei'), 'Gwei'); console.log('Max Fee:', ethers.formatUnits(feeData.maxFeePerGas, 'gwei'), 'Gwei'); console.log('Max Priority Fee:', ethers.formatUnits(feeData.maxPriorityFeePerGas, 'gwei'), 'Gwei'); ``` ### Send ETH ```javascript const tx = await wallet.sendTransaction({ to: '0x...', value: ethers.parseEther('0.1') // 0.1 ETH }); console.log('Transaction hash:', tx.hash); // Wait for confirmation const receipt = await tx.wait(); console.log('Transaction confirmed in block:', receipt.blockNumber); ``` ## Example: Complete Setup ```javascript // index.js import { ethers } from 'ethers'; import { config } from 'dotenv'; config(); // Setup const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); async function main() { // Network info const network = await provider.getNetwork(); console.log('Network:', network.name); console.log('Chain ID:', network.chainId.toString()); // Account info console.log('\nAccount:', wallet.address); const balance = await provider.getBalance(wallet.address); console.log('Balance:', ethers.formatEther(balance), 'ETH'); const nonce = await provider.getTransactionCount(wallet.address); console.log('Nonce:', nonce); // Block info const blockNumber = await provider.getBlockNumber(); console.log('\nLatest block:', blockNumber); const block = await provider.getBlock('latest'); console.log('Block timestamp:', new Date(block.timestamp * 1000).toISOString()); // Gas info const feeData = await provider.getFeeData(); console.log('\nGas Price:', ethers.formatUnits(feeData.gasPrice, 'gwei'), 'Gwei'); } main().catch(console.error); ``` Run the script: ```bash node index.js ``` ## Next Steps * [Reading from Contracts](/docs/builders/ethers/reading) - Query contract data * [Writing to Contracts](/docs/builders/ethers/writing) - Send transactions and interact with contracts # Ethers.js (/docs/builders/frontend/ethers) Ethers.js is a complete and compact library for interacting with the Ethereum blockchain and its ecosystem. It's widely used and battle-tested. ## Why Ethers.js? * **Complete**: Full-featured library with everything you need * **Well-documented**: Extensive documentation and examples * **Stable**: Mature and battle-tested in production * **Popular**: Large community and ecosystem support * **Easy to learn**: Intuitive API design ## Quick Start ```bash npm install ethers ``` ## Basic Setup ```javascript import { ethers } from 'ethers'; // Connect to RISE Testnet const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); // Get the latest block number const blockNumber = await provider.getBlockNumber(); console.log('Current block:', blockNumber); // Create a wallet const wallet = new ethers.Wallet(privateKey, provider); console.log('Wallet address:', wallet.address); // Get balance const balance = await provider.getBalance(wallet.address); console.log('Balance:', ethers.formatEther(balance), 'ETH'); ``` ## Network Configuration ```javascript const RISE_TESTNET = { chainId: 11155931, name: 'RISE Testnet', rpcUrl: 'https://testnet.riselabs.xyz', explorer: 'https://explorer.testnet.riselabs.xyz' }; ``` ## Next Steps } title="Get Started" href="/docs/builders/ethers/get-started" description="Set up your first Ethers.js project" /> } title="Read Contracts" href="/docs/builders/ethers/reading" description="Query blockchain data and contract state" /> } title="Write Contracts" href="/docs/builders/ethers/writing" description="Send transactions and interact with contracts" /> ## Resources * [Official Ethers.js Documentation](https://docs.ethers.org) * [Ethers.js GitHub](https://github.com/ethers-io/ethers.js) * [Migration Guide (v5 to v6)](https://docs.ethers.org/v6/migrating/) # Reading Contract Data (/docs/builders/frontend/ethers/reading) Learn how to read data from smart contracts on RISE using Ethers.js. ## Setup Create a provider and contract instance: ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); const contractAddress = '0x...'; const abi = [ /* contract ABI */ ]; const contract = new ethers.Contract(contractAddress, abi, provider); ``` ## Read Contract Data Call view/pure functions: ```javascript // Simple read const value = await contract.getValue(); console.log('Value:', value.toString()); // Read with arguments const balance = await contract.balanceOf('0x...'); console.log('Balance:', ethers.formatEther(balance)); ``` ## ERC-20 Token Example Read ERC-20 token information: ```javascript const tokenAddress = '0x...'; const abi = [ 'function name() view returns (string)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)', 'function totalSupply() view returns (uint256)', 'function balanceOf(address) view returns (uint256)' ]; const token = new ethers.Contract(tokenAddress, abi, provider); // Get token info const name = await token.name(); const symbol = await token.symbol(); const decimals = await token.decimals(); const totalSupply = await token.totalSupply(); console.log('Token:', name); console.log('Symbol:', symbol); console.log('Decimals:', decimals); console.log('Total Supply:', ethers.formatUnits(totalSupply, decimals)); // Check balance const userAddress = '0x...'; const balance = await token.balanceOf(userAddress); console.log('Balance:', ethers.formatUnits(balance, decimals), symbol); ``` ## Get Contract Events Query past events: ```javascript // Get Transfer events const filter = contract.filters.Transfer(); const events = await contract.queryFilter(filter, 0, 'latest'); console.log('Transfer events:', events.length); events.forEach(event => { console.log({ from: event.args.from, to: event.args.to, value: ethers.formatEther(event.args.value), blockNumber: event.blockNumber, transactionHash: event.transactionHash }); }); ``` ### Filter Events by Parameters ```javascript // Get transfers TO a specific address const toFilter = contract.filters.Transfer(null, '0x...'); const toEvents = await contract.queryFilter(toFilter); // Get transfers FROM a specific address const fromFilter = contract.filters.Transfer('0x...', null); const fromEvents = await contract.queryFilter(fromFilter); // Get specific block range const recentEvents = await contract.queryFilter( contract.filters.Transfer(), -1000, // 1000 blocks ago 'latest' ); ``` ## Listen to Events Subscribe to realtime events: ```javascript // Listen to Transfer events contract.on('Transfer', (from, to, value, event) => { console.log('Transfer detected:'); console.log('From:', from); console.log('To:', to); console.log('Value:', ethers.formatEther(value)); console.log('Block:', event.log.blockNumber); }); // Listen once contract.once('Transfer', (from, to, value) => { console.log('First transfer:', ethers.formatEther(value)); }); // Stop listening contract.removeAllListeners('Transfer'); ``` ## Get Block Data ```javascript // Get latest block const blockNumber = await provider.getBlockNumber(); console.log('Latest block:', blockNumber); // Get block details const block = await provider.getBlock(blockNumber); console.log('Block:', { number: block.number, hash: block.hash, timestamp: block.timestamp, transactions: block.transactions.length, gasUsed: block.gasUsed.toString() }); // Get block with full transactions const blockWithTxs = await provider.getBlock(blockNumber, true); console.log('Transactions:', blockWithTxs.prefetchedTransactions); ``` ## Get Transaction Data ```javascript const txHash = '0x...'; // Get transaction const tx = await provider.getTransaction(txHash); console.log('Transaction:', { from: tx.from, to: tx.to, value: ethers.formatEther(tx.value), gasLimit: tx.gasLimit.toString(), gasPrice: ethers.formatUnits(tx.gasPrice, 'gwei') + ' Gwei', nonce: tx.nonce }); // Get transaction receipt const receipt = await provider.getTransactionReceipt(txHash); console.log('Receipt:', { status: receipt.status, // 1 = success, 0 = failed blockNumber: receipt.blockNumber, gasUsed: receipt.gasUsed.toString(), contractAddress: receipt.contractAddress, // if contract deployment logs: receipt.logs.length }); ``` ## Parse Transaction Logs Decode logs from a transaction: ```javascript const receipt = await provider.getTransactionReceipt(txHash); // Parse logs with contract interface const iface = new ethers.Interface(abi); receipt.logs.forEach(log => { try { const parsedLog = iface.parseLog(log); console.log('Event:', parsedLog.name); console.log('Args:', parsedLog.args); } catch (e) { // Log doesn't match ABI } }); ``` ## Estimate Gas Estimate gas for a function call: ```javascript const gasEstimate = await contract.transfer.estimateGas('0x...', ethers.parseEther('1')); console.log('Estimated gas:', gasEstimate.toString()); // With specific transaction parameters const gasWithParams = await contract.transfer.estimateGas( '0x...', ethers.parseEther('1'), { from: wallet.address } ); ``` ## Static Call (Simulate) Simulate a transaction without sending it: ```javascript // Will revert if the transaction would fail const result = await contract.transfer.staticCall('0x...', ethers.parseEther('1')); console.log('Simulation result:', result); // Useful for functions that return values const mintResult = await contract.mint.staticCall(1000); console.log('Would mint:', mintResult.toString(), 'tokens'); ``` ## Call with Overrides Override transaction parameters for a call: ```javascript const balance = await contract.balanceOf('0x...', { blockTag: 1000000 // Query at specific block }); const historicalBalance = await contract.balanceOf('0x...', { blockTag: 'earliest' // Or 'latest', 'pending' }); ``` ## Example: Complete Token Info ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); const tokenAddress = '0x8a93d247134d91e0de6f96547cb0204e5be8e5d8'; // USDC const abi = [ 'function name() view returns (string)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)', 'function totalSupply() view returns (uint256)', 'function balanceOf(address) view returns (uint256)', 'event Transfer(address indexed from, address indexed to, uint256 value)' ]; const token = new ethers.Contract(tokenAddress, abi, provider); async function getTokenInfo() { // Get basic info const [name, symbol, decimals, totalSupply] = await Promise.all([ token.name(), token.symbol(), token.decimals(), token.totalSupply() ]); console.log('Token Information:'); console.log('Name:', name); console.log('Symbol:', symbol); console.log('Decimals:', decimals); console.log('Total Supply:', ethers.formatUnits(totalSupply, decimals)); // Get recent transfers const transferFilter = token.filters.Transfer(); const recentTransfers = await token.queryFilter(transferFilter, -1000, 'latest'); console.log('\nRecent Transfers:', recentTransfers.length); // Show last 5 transfers recentTransfers.slice(-5).forEach(event => { console.log({ from: event.args.from, to: event.args.to, value: ethers.formatUnits(event.args.value, decimals), block: event.blockNumber }); }); } getTokenInfo().catch(console.error); ``` ## Next Steps * [Writing to Contracts](/docs/builders/ethers/writing) - Send transactions and modify state * [Contract Addresses](/docs/builders/contract-addresses) - Find deployed contract addresses * [Testnet Tokens](/docs/builders/testnet-tokens) - Get testnet tokens for testing # Writing to Contracts (/docs/builders/frontend/ethers/writing) Learn how to send transactions and write to smart contracts on RISE using Ethers.js. ## Setup Signer Create a wallet to sign transactions: ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); const wallet = new ethers.Wallet('YOUR_PRIVATE_KEY', provider); console.log('Signing transactions as:', wallet.address); ``` ## Send ETH Transfer ETH to another address: ```javascript const tx = await wallet.sendTransaction({ to: '0x...', value: ethers.parseEther('0.1') // 0.1 ETH }); console.log('Transaction hash:', tx.hash); // Wait for confirmation const receipt = await tx.wait(); console.log('Transaction confirmed in block:', receipt.blockNumber); console.log('Gas used:', receipt.gasUsed.toString()); ``` ## Write to Contract Call a contract function that modifies state: ```javascript const contractAddress = '0x...'; const abi = [ 'function transfer(address to, uint256 amount) returns (bool)' ]; const contract = new ethers.Contract(contractAddress, abi, wallet); // Send transaction const tx = await contract.transfer('0x...', ethers.parseEther('10')); console.log('Transaction hash:', tx.hash); // Wait for confirmation const receipt = await tx.wait(); console.log('Transaction confirmed!'); console.log('Status:', receipt.status === 1 ? 'Success' : 'Failed'); ``` ## Deploy Contract Deploy a new smart contract: ```javascript const abi = [ /* contract ABI */ ]; const bytecode = '0x...'; // Contract bytecode // Create contract factory const ContractFactory = new ethers.ContractFactory(abi, bytecode, wallet); // Deploy contract const contract = await ContractFactory.deploy( // constructor arguments 'My Token', 'MTK', 18 ); console.log('Deploying contract...'); console.log('Transaction hash:', contract.deploymentTransaction().hash); // Wait for deployment await contract.waitForDeployment(); const address = await contract.getAddress(); console.log('Contract deployed at:', address); ``` ## Wait for Confirmations Wait for multiple confirmations: ```javascript const tx = await contract.transfer('0x...', ethers.parseEther('10')); console.log('Transaction sent:', tx.hash); // Wait for 1 confirmation (default) const receipt1 = await tx.wait(); console.log('1 confirmation'); // Wait for 3 confirmations const receipt3 = await tx.wait(3); console.log('3 confirmations'); ``` ## Set Gas Parameters Control gas settings: ```javascript // Estimate gas first const gasEstimate = await contract.transfer.estimateGas( '0x...', ethers.parseEther('10') ); console.log('Estimated gas:', gasEstimate.toString()); // Send with gas limit const tx = await contract.transfer( '0x...', ethers.parseEther('10'), { gasLimit: gasEstimate * 120n / 100n // Add 20% buffer } ); ``` ### EIP-1559 Transactions ```javascript const feeData = await provider.getFeeData(); const tx = await contract.transfer( '0x...', ethers.parseEther('10'), { maxFeePerGas: feeData.maxFeePerGas, maxPriorityFeePerGas: feeData.maxPriorityFeePerGas } ); ``` ## Sign Messages Sign a message with your private key: ```javascript const message = 'Hello RISE!'; const signature = await wallet.signMessage(message); console.log('Signature:', signature); // Verify signature const recoveredAddress = ethers.verifyMessage(message, signature); console.log('Signer:', recoveredAddress); console.log('Valid:', recoveredAddress === wallet.address); ``` ## Sign Typed Data (EIP-712) Sign structured data: ```javascript const domain = { name: 'My Dapp', version: '1', chainId: 11155931, verifyingContract: '0x...' }; const types = { Mail: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'contents', type: 'string' } ] }; const value = { from: wallet.address, to: '0x...', contents: 'Hello!' }; const signature = await wallet.signTypedData(domain, types, value); console.log('Signature:', signature); ``` ## Handle Transaction Errors Properly handle errors: ```javascript try { const tx = await contract.transfer('0x...', ethers.parseEther('10')); const receipt = await tx.wait(); if (receipt.status === 0) { console.log('Transaction failed'); } else { console.log('Transaction successful'); } } catch (error) { if (error.code === 'INSUFFICIENT_FUNDS') { console.error('Not enough funds'); } else if (error.code === 'UNPREDICTABLE_GAS_LIMIT') { console.error('Transaction will likely fail'); } else if (error.code === 'NONCE_EXPIRED') { console.error('Nonce already used'); } else { console.error('Transaction failed:', error.message); } } ``` ## Cancel/Replace Transaction Replace a pending transaction: ```javascript // Send initial transaction const tx = await wallet.sendTransaction({ to: '0x...', value: ethers.parseEther('0.1') }); console.log('Transaction sent:', tx.hash); // Replace with higher gas price (same nonce) const nonce = await provider.getTransactionCount(wallet.address, 'pending'); const feeData = await provider.getFeeData(); const replacementTx = await wallet.sendTransaction({ to: '0x...', value: ethers.parseEther('0.1'), nonce: tx.nonce, maxFeePerGas: feeData.maxFeePerGas * 2n, // Double the fee maxPriorityFeePerGas: feeData.maxPriorityFeePerGas * 2n }); console.log('Replacement transaction:', replacementTx.hash); ``` ## Batch Transactions Send multiple transactions: ```javascript const recipients = ['0x...', '0x...', '0x...']; const amount = ethers.parseEther('0.1'); const transactions = await Promise.all( recipients.map(async (recipient, i) => { const nonce = await provider.getTransactionCount(wallet.address, 'latest') + i; return wallet.sendTransaction({ to: recipient, value: amount, nonce }); }) ); console.log('Transactions sent:', transactions.map(tx => tx.hash)); // Wait for all confirmations const receipts = await Promise.all(transactions.map(tx => tx.wait())); console.log('All transactions confirmed'); ``` ## Example: ERC-20 Token Operations ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); const tokenAddress = '0x8a93d247134d91e0de6f96547cb0204e5be8e5d8'; // USDC const abi = [ 'function transfer(address to, uint256 amount) returns (bool)', 'function approve(address spender, uint256 amount) returns (bool)', 'function transferFrom(address from, address to, uint256 amount) returns (bool)', 'function balanceOf(address) view returns (uint256)', 'function allowance(address owner, address spender) view returns (uint256)' ]; const token = new ethers.Contract(tokenAddress, abi, wallet); async function tokenOperations() { const recipient = '0x...'; const amount = ethers.parseUnits('10', 6); // 10 USDC (6 decimals) // Check balance const balance = await token.balanceOf(wallet.address); console.log('Balance:', ethers.formatUnits(balance, 6), 'USDC'); if (balance < amount) { console.error('Insufficient balance'); return; } // Transfer tokens console.log('Transferring 10 USDC...'); const transferTx = await token.transfer(recipient, amount); console.log('Transaction hash:', transferTx.hash); const transferReceipt = await transferTx.wait(); console.log('Transfer confirmed in block:', transferReceipt.blockNumber); // Approve spending const spender = '0x...'; console.log('Approving spender...'); const approveTx = await token.approve(spender, amount); await approveTx.wait(); console.log('Approval confirmed'); // Check allowance const allowance = await token.allowance(wallet.address, spender); console.log('Allowance:', ethers.formatUnits(allowance, 6), 'USDC'); } tokenOperations().catch(console.error); ``` ## Example: Contract Deployment ```javascript import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://testnet.riselabs.xyz'); const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider); // Simple ERC20 contract const abi = [ 'constructor(string name, string symbol, uint256 initialSupply)', 'function name() view returns (string)', 'function symbol() view returns (string)', 'function totalSupply() view returns (uint256)', 'function balanceOf(address) view returns (uint256)', 'function transfer(address to, uint256 amount) returns (bool)' ]; const bytecode = '0x...'; // Your compiled bytecode async function deployToken() { console.log('Deploying from:', wallet.address); // Get balance const balance = await provider.getBalance(wallet.address); console.log('Balance:', ethers.formatEther(balance), 'ETH'); // Create factory const factory = new ethers.ContractFactory(abi, bytecode, wallet); // Deploy console.log('Deploying token...'); const token = await factory.deploy( 'My Token', 'MTK', ethers.parseEther('1000000') // 1 million tokens ); console.log('Deployment transaction:', token.deploymentTransaction().hash); // Wait for deployment await token.waitForDeployment(); const address = await token.getAddress(); console.log('Token deployed at:', address); console.log('Explorer:', `https://explorer.testnet.riselabs.xyz/address/${address}`); // Verify deployment const name = await token.name(); const symbol = await token.symbol(); const totalSupply = await token.totalSupply(); console.log('\nToken Info:'); console.log('Name:', name); console.log('Symbol:', symbol); console.log('Total Supply:', ethers.formatEther(totalSupply)); } deployToken().catch(console.error); ``` ## Next Steps * [Contract Addresses](/docs/builders/contract-addresses) - Find deployed contract addresses on RISE * [Testnet Tokens](/docs/builders/testnet-tokens) - Get testnet tokens for testing * [Network Details](/docs/builders/network-details) - RISE network configuration # Get Started with Viem (/docs/builders/frontend/viem/get-started) Learn how to set up and configure Viem for building on RISE. ## Installation Install Viem using your preferred package manager: ```bash npm install viem ``` ## Project Setup Create a new TypeScript project: ```bash mkdir my-rise-viem-project cd my-rise-viem-project npm init -y npm install --save-dev typescript @types/node npx tsc --init ``` ## Import RISE Testnet RISE Testnet is available in Viem's built-in chains: ```typescript import { riseTestnet } from 'viem/chains' ``` That's it! No need to manually configure the chain. ## Create Clients ### Public Client (Read-only) For reading blockchain data: ```typescript import { createPublicClient, http } from 'viem' import { riseTestnet } from 'viem/chains' const publicClient = createPublicClient({ chain: riseTestnet, transport: http() }) // Get block number const blockNumber = await publicClient.getBlockNumber() console.log('Block number:', blockNumber) // Get account balance const balance = await publicClient.getBalance({ address: '0x...' }) console.log('Balance:', balance) ``` ### Wallet Client (Read & Write) For sending transactions: ```typescript import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { riseTestnet } from 'viem/chains' const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY') const walletClient = createWalletClient({ account, chain: riseTestnet, transport: http() }) // Send a transaction const hash = await walletClient.sendTransaction({ to: '0x...', value: 1000000000000000000n // 1 ETH }) console.log('Transaction hash:', hash) ``` ## Environment Variables Create a `.env` file for sensitive data: ```bash PRIVATE_KEY=0x... RPC_URL=https://testnet.riselabs.xyz ``` Load environment variables: ```typescript import { config } from 'dotenv' config() const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) ``` ## Example: Complete Setup ```typescript // index.ts import { createPublicClient, createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { riseTestnet } from 'viem/chains' // Initialize clients const publicClient = createPublicClient({ chain: riseTestnet, transport: http() }) const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const walletClient = createWalletClient({ account, chain: riseTestnet, transport: http() }) // Get account info const address = account.address const balance = await publicClient.getBalance({ address }) const blockNumber = await publicClient.getBlockNumber() console.log('Address:', address) console.log('Balance:', balance) console.log('Latest block:', blockNumber) ``` ## Next Steps * [Reading from Contracts](/docs/builders/viem/reading) - Query contract data * [Writing to Contracts](/docs/builders/viem/writing) - Send transactions and interact with contracts # Viem (/docs/builders/frontend/viem) Viem is a TypeScript interface for Ethereum that provides low-level stateless primitives for interacting with Ethereum. It's lightweight, modular, and type-safe. ## Why Viem? * **Type-safe**: First-class TypeScript support with inferred types * **Lightweight**: Tree-shakeable with minimal dependencies * **Fast**: Optimized for performance * **Modular**: Only import what you need * **Modern**: Built with latest JavaScript features ## Quick Start ```bash npm install viem ``` ## Basic Setup ```typescript import { createPublicClient, http } from 'viem' import { riseTestnet } from 'viem/chains' // Create a public client const client = createPublicClient({ chain: riseTestnet, transport: http() }) // Get the latest block number const blockNumber = await client.getBlockNumber() console.log('Current block:', blockNumber) ``` ## Next Steps } title="Get Started" href="/docs/builders/frontend/viem/get-started" description="Set up your first Viem project" /> } title="Read Contracts" href="/docs/builders/frontend/viem/reading" description="Query blockchain data and contract state" /> } title="Write Contracts" href="/docs/builders/frontend/viem/writing" description="Send transactions and interact with contracts" /> ## Resources * [Official Viem Documentation](https://viem.sh) * [Viem GitHub](https://github.com/wevm/viem) * [TypeScript Support](https://viem.sh/docs/typescript) # Reading Contract Data (/docs/builders/frontend/viem/reading) Learn how to read data from smart contracts on RISE using Viem. ## Setup First, create a public client: ```typescript import { createPublicClient, http } from 'viem' import { riseTestnet } from './config' const client = createPublicClient({ chain: riseTestnet, transport: http() }) ``` ## Read Contract Use `readContract` to call view/pure functions: ```typescript const result = await client.readContract({ address: '0x...', abi: [ { name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }], outputs: [{ type: 'uint256' }], }, ], functionName: 'balanceOf', args: ['0x...'] }) console.log('Balance:', result) ``` ## Multiple Reads (Multicall) Batch multiple contract reads efficiently: ```typescript import { parseAbi } from 'viem' const abi = parseAbi([ 'function name() view returns (string)', 'function symbol() view returns (string)', 'function totalSupply() view returns (uint256)', ]) const results = await client.multicall({ contracts: [ { address: '0x...', abi, functionName: 'name', }, { address: '0x...', abi, functionName: 'symbol', }, { address: '0x...', abi, functionName: 'totalSupply', }, ], }) console.log('Name:', results[0].result) console.log('Symbol:', results[1].result) console.log('Total Supply:', results[2].result) ``` ## Get Contract Events Query past events from a contract: ```typescript import { parseAbiItem } from 'viem' const logs = await client.getLogs({ address: '0x...', event: parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'), fromBlock: 0n, toBlock: 'latest' }) console.log('Transfer events:', logs) ``` ## Watch Contract Events Subscribe to realtime contract events: ```typescript const unwatch = client.watchContractEvent({ address: '0x...', abi: parseAbi(['event Transfer(address indexed from, address indexed to, uint256 value)']), eventName: 'Transfer', onLogs: logs => { logs.forEach(log => { console.log('Transfer:', { from: log.args.from, to: log.args.to, value: log.args.value }) }) } }) // Stop watching // unwatch() ``` ## Get Block Information ```typescript // Get latest block const block = await client.getBlock() console.log('Latest block:', block.number) // Get specific block const specificBlock = await client.getBlock({ blockNumber: 1000000n }) // Get block with transactions const blockWithTxs = await client.getBlock({ blockNumber: 1000000n, includeTransactions: true }) ``` ## Get Transaction Data ```typescript // Get transaction by hash const transaction = await client.getTransaction({ hash: '0x...' }) console.log('Transaction:', transaction) // Get transaction receipt const receipt = await client.getTransactionReceipt({ hash: '0x...' }) console.log('Receipt:', receipt) console.log('Status:', receipt.status) // 'success' or 'reverted' ``` ## Estimate Gas Estimate gas for a contract call: ```typescript const gasEstimate = await client.estimateContractGas({ address: '0x...', abi: parseAbi(['function mint(address to, uint256 amount)']), functionName: 'mint', args: ['0x...', 1000000000000000000n], account: '0x...' }) console.log('Estimated gas:', gasEstimate) ``` ## Example: ERC-20 Token Info ```typescript import { parseAbi, formatUnits } from 'viem' const tokenAddress = '0x...' const userAddress = '0x...' const abi = parseAbi([ 'function name() view returns (string)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)', 'function totalSupply() view returns (uint256)', 'function balanceOf(address) view returns (uint256)', ]) // Batch read all token info const [name, symbol, decimals, totalSupply, balance] = await Promise.all([ client.readContract({ address: tokenAddress, abi, functionName: 'name', }), client.readContract({ address: tokenAddress, abi, functionName: 'symbol', }), client.readContract({ address: tokenAddress, abi, functionName: 'decimals', }), client.readContract({ address: tokenAddress, abi, functionName: 'totalSupply', }), client.readContract({ address: tokenAddress, abi, functionName: 'balanceOf', args: [userAddress], }), ]) console.log({ name, symbol, decimals, totalSupply: formatUnits(totalSupply, decimals), balance: formatUnits(balance, decimals) }) ``` ## Next Steps * [Writing to Contracts](/docs/builders/viem/writing) - Send transactions and modify state * [Contract Addresses](/docs/builders/contract-addresses) - Find deployed contract addresses # Writing to Contracts (/docs/builders/frontend/viem/writing) Learn how to send transactions and write to smart contracts on RISE using Viem. ## Setup Wallet Client Create a wallet client with your account: ```typescript import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { riseTestnet } from './config' const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY') const walletClient = createWalletClient({ account, chain: riseTestnet, transport: http() }) ``` ## Send ETH Transfer ETH to another address: ```typescript const hash = await walletClient.sendTransaction({ to: '0x...', value: 1000000000000000000n, // 1 ETH in wei }) console.log('Transaction hash:', hash) ``` ## Write to Contract Call a contract function that modifies state: ```typescript import { parseAbi } from 'viem' const abi = parseAbi([ 'function transfer(address to, uint256 amount) returns (bool)', ]) const hash = await walletClient.writeContract({ address: '0x...', // Contract address abi, functionName: 'transfer', args: ['0x...', 1000000000000000000n], // recipient, amount }) console.log('Transaction hash:', hash) ``` ## Wait for Transaction Wait for transaction confirmation: ```typescript import { createPublicClient, http } from 'viem' const publicClient = createPublicClient({ chain: riseTestnet, transport: http() }) // Send transaction const hash = await walletClient.writeContract({ address: '0x...', abi, functionName: 'mint', args: [1000n], }) // Wait for confirmation const receipt = await publicClient.waitForTransactionReceipt({ hash }) console.log('Transaction confirmed!') console.log('Block number:', receipt.blockNumber) console.log('Gas used:', receipt.gasUsed) console.log('Status:', receipt.status) // 'success' or 'reverted' ``` ## Deploy Contract Deploy a new smart contract: ```typescript import { parseAbi } from 'viem' const abi = parseAbi([ 'constructor(string name, string symbol)', ]) const bytecode = '0x...' // Contract bytecode const hash = await walletClient.deployContract({ abi, bytecode, args: ['My Token', 'MTK'], }) // Get deployed contract address const receipt = await publicClient.waitForTransactionReceipt({ hash }) console.log('Contract deployed at:', receipt.contractAddress) ``` ## Sign Messages Sign a message with your private key: ```typescript const signature = await walletClient.signMessage({ message: 'Hello RISE!', }) console.log('Signature:', signature) ``` ## Sign Typed Data (EIP-712) Sign structured data: ```typescript const signature = await walletClient.signTypedData({ domain: { name: 'My Dapp', version: '1', chainId: 11155931, verifyingContract: '0x...', }, types: { Mail: [ { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'contents', type: 'string' }, ], }, primaryType: 'Mail', message: { from: '0x...', to: '0x...', contents: 'Hello!', }, }) console.log('Signature:', signature) ``` ## Gas Estimation & Control ### Estimate Gas ```typescript const gas = await publicClient.estimateContractGas({ address: '0x...', abi, functionName: 'transfer', args: ['0x...', 1000n], account, }) console.log('Estimated gas:', gas) ``` ### Set Gas Parameters ```typescript const hash = await walletClient.writeContract({ address: '0x...', abi, functionName: 'transfer', args: ['0x...', 1000n], gas: 100000n, // Gas limit }) ``` ## Error Handling Handle transaction errors: ```typescript try { const hash = await walletClient.writeContract({ address: '0x...', abi, functionName: 'transfer', args: ['0x...', 1000n], }) const receipt = await publicClient.waitForTransactionReceipt({ hash }) if (receipt.status === 'success') { console.log('Transaction successful!') } else { console.log('Transaction reverted') } } catch (error) { if (error.name === 'ContractFunctionExecutionError') { console.error('Contract execution failed:', error.message) } else if (error.name === 'InsufficientFundsError') { console.error('Insufficient funds') } else { console.error('Transaction failed:', error) } } ``` ## Simulate Transaction Test a transaction before sending: ```typescript const { result } = await publicClient.simulateContract({ address: '0x...', abi, functionName: 'transfer', args: ['0x...', 1000n], account, }) console.log('Simulation result:', result) // If simulation succeeds, send the transaction const hash = await walletClient.writeContract({ address: '0x...', abi, functionName: 'transfer', args: ['0x...', 1000n], }) ``` ## Example: Complete ERC-20 Transfer ```typescript import { createPublicClient, createWalletClient, http, parseAbi, formatUnits } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { riseTestnet } from './config' // Setup const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`) const publicClient = createPublicClient({ chain: riseTestnet, transport: http(), }) const walletClient = createWalletClient({ account, chain: riseTestnet, transport: http(), }) const tokenAddress = '0x...' const recipientAddress = '0x...' const amount = 1000000000000000000n // 1 token (18 decimals) const abi = parseAbi([ 'function transfer(address to, uint256 amount) returns (bool)', 'function balanceOf(address) view returns (uint256)', ]) // Check balance before const balanceBefore = await publicClient.readContract({ address: tokenAddress, abi, functionName: 'balanceOf', args: [account.address], }) console.log('Balance before:', formatUnits(balanceBefore, 18)) // Send transfer const hash = await walletClient.writeContract({ address: tokenAddress, abi, functionName: 'transfer', args: [recipientAddress, amount], }) console.log('Transaction hash:', hash) // Wait for confirmation const receipt = await publicClient.waitForTransactionReceipt({ hash }) console.log('Transaction confirmed in block:', receipt.blockNumber) // Check balance after const balanceAfter = await publicClient.readContract({ address: tokenAddress, abi, functionName: 'balanceOf', args: [account.address], }) console.log('Balance after:', formatUnits(balanceAfter, 18)) ``` ## Next Steps * [Contract Addresses](/docs/builders/contract-addresses) - Find deployed contract addresses on RISE * [Testnet Tokens](/docs/builders/testnet-tokens) - Get testnet tokens for testing # Compiling Contracts (/docs/builders/smart-contracts/foundry/compiling) import { Step, Steps } from 'fumadocs-ui/components/steps'; Compile your Solidity contracts using Foundry's `forge build` command. ## Configuration Configure the Solidity compiler in your `foundry.toml`: ```toml title="foundry.toml" [profile.default] src = "src" out = "out" libs = ["lib"] solc = "0.8.30" # Optimizer settings optimizer = true optimizer_runs = 200 [rpc_endpoints] rise = "https://testnet.riselabs.xyz" [etherscan] rise = { key = "", url = "https://explorer.testnet.riselabs.xyz/api" } ``` ## Compile ### Run Build Compile all contracts in the `src/` directory: ```bash forge build ``` ### Check Artifacts Compilation generates artifacts in the `out/` directory containing: * Contract ABI * Bytecode * Metadata The compiled JSON files can be found at: ``` out/YourContract.sol/YourContract.json ``` ## Compiler Options ### Specify Solidity Version ```bash forge build --use 0.8.30 ``` ### Enable Optimizer ```bash forge build --optimize --optimizer-runs 200 ``` ### Watch Mode Automatically recompile on file changes: ```bash forge build --watch ``` ## Clean and Rebuild To force a fresh compilation: ```bash forge clean forge build ``` ## Check Contract Sizes Ensure your contracts are within the 24KB size limit: ```bash forge build --sizes ``` Output shows each contract's size. ## Next Steps # Deploying Contracts (/docs/builders/smart-contracts/foundry/deploying) import { Step, Steps } from 'fumadocs-ui/components/steps'; Deploy your compiled smart contracts to the RISE Testnet using Foundry's `forge create` or deployment scripts. ## Prerequisites * Foundry project configured for RISE (see [Get Started](/docs/builders/smart-contracts/foundry/get-started)) * Testnet ETH from the [RISE Faucet](https://faucet.testnet.riselabs.xyz/) * Private key set in environment variables ## Deploy with forge create The simplest way to deploy a contract: ```bash forge create \ --rpc-url https://testnet.riselabs.xyz \ --private-key $PRIVATE_KEY \ src/Counter.sol:Counter ``` Output: ``` Deployer: 0x1234... Deployed to: 0xabcd... Transaction hash: 0x5678... ``` ### With Constructor Arguments If your contract has constructor arguments: ```bash forge create \ --rpc-url https://testnet.riselabs.xyz \ --private-key $PRIVATE_KEY \ src/Lock.sol:Lock \ --constructor-args 1706745600 ``` ### With ETH Value To send ETH during deployment: ```bash forge create \ --rpc-url https://testnet.riselabs.xyz \ --private-key $PRIVATE_KEY \ src/Lock.sol:Lock \ --constructor-args 1706745600 \ --value 0.001ether ``` ## Deploy with Scripts For more complex deployments, use Forge scripts: ### Create Deploy Script Create a deployment script in `script/Counter.s.sol`: ```solidity title="script/Counter.s.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Script, console} from "forge-std/Script.sol"; import {Counter} from "../src/Counter.sol"; contract CounterScript is Script { function run() public { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); Counter counter = new Counter(); console.log("Counter deployed to:", address(counter)); vm.stopBroadcast(); } } ``` ### Run Deployment Script Deploy to RISE Testnet: ```bash forge script script/Counter.s.sol:CounterScript \ --rpc-url https://testnet.riselabs.xyz \ --broadcast ``` Add `-vvvv` for verbose output to see transaction details. ### View on Explorer View your deployed contract on the [RISE Testnet Explorer](https://explorer.testnet.riselabs.xyz) by searching for the contract address. ## Deploy and Verify Deploy and verify in a single command: ```bash forge create \ --rpc-url https://testnet.riselabs.xyz \ --private-key $PRIVATE_KEY \ src/Counter.sol:Counter \ --verify \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api/ ``` ## Using RPC Endpoint Aliases If you configured `foundry.toml` with RPC endpoints, use the alias: ```bash forge create \ --rpc-url rise \ --private-key $PRIVATE_KEY \ src/Counter.sol:Counter ``` ## Next Steps # Get Started (/docs/builders/smart-contracts/foundry/get-started) import { Step, Steps } from 'fumadocs-ui/components/steps'; [Foundry](https://book.getfoundry.sh/) is a blazing fast, portable, and modular toolkit for Ethereum application development written in Rust. ## Prerequisites * A wallet with testnet ETH from the [RISE Faucet](https://faucet.testnet.riselabs.xyz/) ## Install Foundry ```bash curl -L https://foundry.paradigm.xyz | bash foundryup ``` This installs `forge`, `cast`, `anvil`, and `chisel`. ## Create a Project ### Initialize Project Create a new Foundry project: ```bash forge init my-rise-project cd my-rise-project ``` This creates a project with: * `src/` - Your smart contracts * `test/` - Your tests * `script/` - Deployment scripts * `foundry.toml` - Configuration file ### Configure for RISE Update your `foundry.toml` to add the RISE network: ```toml title="foundry.toml" [profile.default] src = "src" out = "out" libs = ["lib"] solc = "0.8.30" [rpc_endpoints] rise = "https://testnet.riselabs.xyz" ``` ### Set Environment Variables Create a `.env` file for your private key: ```bash title=".env" PRIVATE_KEY=your_private_key_here ``` Load it in your shell: ```bash source .env ``` **Never commit your `.env` file to version control.** ### Review the Default Contract Foundry creates a default `Counter.sol` contract: ```solidity title="src/Counter.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; contract Counter { uint256 public number; function setNumber(uint256 newNumber) public { number = newNumber; } function increment() public { number++; } } ``` ### Build the Project Compile your contracts: ```bash forge build ``` This generates artifacts in the `out/` directory. ## Next Steps # Foundry (/docs/builders/smart-contracts/foundry) [Foundry](https://book.getfoundry.sh/) is a blazing fast, portable, and modular toolkit for Ethereum application development written in Rust. RISE is fully EVM compatible, so you can use Foundry with minimal configuration changes. ## Quick Links # Testing Contracts (/docs/builders/smart-contracts/foundry/testing) import { Step, Steps } from 'fumadocs-ui/components/steps'; Foundry includes a powerful testing framework that lets you write tests in Solidity. Tests run extremely fast compared to JavaScript-based testing frameworks. ## Write Tests ### Create Test File Create a test file in the `test/` directory: ```solidity title="test/Counter.t.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; import {Test, console} from "forge-std/Test.sol"; import {Counter} from "../src/Counter.sol"; contract CounterTest is Test { Counter public counter; function setUp() public { counter = new Counter(); } function test_InitialValue() public view { assertEq(counter.number(), 0); } function test_Increment() public { counter.increment(); assertEq(counter.number(), 1); } function test_SetNumber() public { counter.setNumber(42); assertEq(counter.number(), 42); } function testFuzz_SetNumber(uint256 x) public { counter.setNumber(x); assertEq(counter.number(), x); } } ``` ### Run Tests Execute your tests: ```bash forge test ``` Output: ``` Running 4 tests for test/Counter.t.sol:CounterTest [PASS] test_InitialValue() (gas: 5453) [PASS] test_Increment() (gas: 28334) [PASS] test_SetNumber() (gas: 28312) [PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 27564, ~: 28387) Test result: ok. 4 passed; 0 failed; finished in 10.23ms ``` ## Verbose Output See detailed test output with `-v` flags: ```bash forge test -vv # Show logs forge test -vvv # Show execution traces forge test -vvvv # Show full traces including setup ``` ## Gas Reports Generate gas usage reports: ```bash forge test --gas-report ``` ## Run Specific Tests Run a single test file: ```bash forge test --match-path test/Counter.t.sol ``` Run tests matching a pattern: ```bash forge test --match-test test_Increment ``` Run tests in a specific contract: ```bash forge test --match-contract CounterTest ``` ## Fuzz Testing Foundry automatically runs fuzz tests on functions prefixed with `testFuzz_`: ```solidity function testFuzz_SetNumber(uint256 x) public { counter.setNumber(x); assertEq(counter.number(), x); } ``` Configure fuzz runs in `foundry.toml`: ```toml [fuzz] runs = 1000 ``` ## Cheatcodes Foundry provides cheatcodes for testing advanced scenarios: ```solidity // Set block timestamp vm.warp(1641070800); // Set msg.sender vm.prank(address(0x1234)); // Expect a revert vm.expectRevert("Error message"); // Deal ETH to an address vm.deal(address(0x1234), 1 ether); ``` ## Next Steps # Verifying Contracts (/docs/builders/smart-contracts/foundry/verifying) import { Step, Steps } from 'fumadocs-ui/components/steps'; Verify your deployed contracts on the RISE Testnet Explorer to make the source code publicly viewable. ## Configuration Configure verification in your `foundry.toml`: ```toml title="foundry.toml" [profile.default] src = "src" out = "out" libs = ["lib"] solc = "0.8.30" [rpc_endpoints] rise = "https://testnet.riselabs.xyz" ``` RISE uses Blockscout for contract verification. You don't need an API key - just specify `--verifier blockscout` when verifying. ## Deploy and Verify The easiest way is to deploy and verify in a single command: ```bash forge create \ --rpc-url https://testnet.riselabs.xyz \ --private-key $PRIVATE_KEY \ src/Counter.sol:Counter \ --verify \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api/ ``` ## Verify Existing Contract To verify an already deployed contract: ```bash forge verify-contract \ --rpc-url https://testnet.riselabs.xyz \ \ src/Counter.sol:Counter \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api/ ``` ### With Constructor Arguments If your contract has constructor arguments: ```bash forge verify-contract \ --rpc-url https://testnet.riselabs.xyz \ \ src/Lock.sol:Lock \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api/ \ --constructor-args $(cast abi-encode "constructor(uint256)" 1706745600) ``` ## Using Config Aliases If you configured the RPC endpoint in `foundry.toml`, you can use the alias: ```bash forge verify-contract \ --rpc-url rise \ \ src/Counter.sol:Counter \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api/ ``` ## Verify via Script Verify contracts deployed via scripts by adding the `--verify` flag: ```bash forge script script/Counter.s.sol:CounterScript \ --rpc-url https://testnet.riselabs.xyz \ --broadcast \ --verify \ --verifier blockscout \ --verifier-url https://explorer.testnet.riselabs.xyz/api/ ``` ## Important Notes * The RISE explorer uses Blockscout, so always specify `--verifier blockscout` * Constructor arguments are ABI-encoded - use `cast abi-encode` to encode them * Never store private keys in source code ## View Verified Contract After verification, view your contract on the [RISE Testnet Explorer](https://explorer.testnet.riselabs.xyz). The "Contract" tab will show your source code and allow direct interaction with contract functions. # Compiling Contracts (/docs/builders/smart-contracts/hardhat/compiling) import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Compile your Solidity contracts to bytecode and ABI for deployment on RISE. ## Configuration Ensure your `hardhat.config.ts` has the Solidity compiler configured: ```typescript title="hardhat.config.ts" import "dotenv/config"; import { defineConfig } from "hardhat/config"; import hardhatToolboxMochaEthers from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; export default defineConfig({ plugins: [hardhatToolboxMochaEthers], solidity: { version: "0.8.30", settings: { optimizer: { enabled: true, runs: 200 } } }, networks: { rise: { type: "http", url: process.env.RISE_RPC_URL || "https://testnet.riselabs.xyz", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [], chainId: 11155931 } } }); ``` ## Compile ### Run Build Compile all contracts in the `contracts/` directory: ```bash npx hardhat build ``` ```bash yarn hardhat build ``` ```bash pnpm exec hardhat build ``` ```bash bun x hardhat build ``` ### Check Artifacts Compilation generates artifacts in the `artifacts/` directory containing: * Contract ABI * Bytecode * Source maps for debugging The compiled JSON files can be found at: ``` artifacts/contracts/YourContract.sol/YourContract.json ``` ## Multiple Compiler Versions If you have contracts requiring different Solidity versions: ```typescript title="hardhat.config.ts" export default defineConfig({ solidity: { compilers: [ { version: "0.8.30" }, { version: "0.7.6" } ] } }); ``` ## Clean and Recompile To force a fresh compilation: ```bash npx hardhat clean npx hardhat build ``` ```bash yarn hardhat clean yarn hardhat build ``` ```bash pnpm exec hardhat clean pnpm exec hardhat build ``` ```bash bun x hardhat clean bun x hardhat build ``` ## Next Steps # Deploying Contracts (/docs/builders/smart-contracts/hardhat/deploying) import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Deploy your compiled smart contracts to the RISE Testnet using Hardhat Ignition, our official deployment plugin. ## Prerequisites * Hardhat project configured for RISE (see [Get Started](/docs/builders/smart-contracts/hardhat/get-started)) * Testnet ETH from the [RISE Faucet](https://faucet.testnet.riselabs.xyz/) * Private key configured with Configuration Variables ## Deploy with Hardhat Ignition ### Write a Deployment Module Hardhat Ignition uses modules to describe deployments. Create `ignition/modules/Counter.ts`: ```typescript title="ignition/modules/Counter.ts" import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("CounterModule", (m) => { const counter = m.contract("Counter"); return { counter }; }); ``` This module deploys an instance of the `Counter` contract. You can also call functions after deployment: ```typescript title="ignition/modules/Counter.ts" import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("CounterModule", (m) => { const counter = m.contract("Counter"); // Call a function after deployment m.call(counter, "setNumber", [42n]); return { counter }; }); ``` ### Test Your Module Locally Before deploying to a live network, test the module locally: ```bash npx hardhat ignition deploy ignition/modules/Counter.ts ``` ```bash yarn hardhat ignition deploy ignition/modules/Counter.ts ``` ```bash pnpm exec hardhat ignition deploy ignition/modules/Counter.ts ``` ```bash bun x hardhat ignition deploy ignition/modules/Counter.ts ``` This simulates the deployment in a local network to verify everything works. ### Deploy to RISE Testnet Deploy your contract to RISE: ```bash npx hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet ``` ```bash yarn hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet ``` ```bash pnpm exec hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet ``` ```bash bun x hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet ``` You'll see output indicating successful deployment with the contract address. Ignition remembers deployment state. If you run the same deployment again, nothing will happen because the module was already executed. ### View on Explorer View your deployed contract on the [RISE Testnet Explorer](https://explorer.testnet.riselabs.xyz) by searching for the contract address from the deployment output. ## Deploy with Constructor Arguments If your contract has constructor parameters: ```typescript title="ignition/modules/Counter.ts" import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; export default buildModule("CounterModule", (m) => { const initialValue = m.getParameter("initialValue", 42n); const counter = m.contract("Counter", [initialValue]); return { counter }; }); ``` Deploy with parameters: ```bash npx hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --parameters '{"CounterModule":{"initialValue":"100"}}' ``` ```bash yarn hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --parameters '{"CounterModule":{"initialValue":"100"}}' ``` ```bash pnpm exec hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --parameters '{"CounterModule":{"initialValue":"100"}}' ``` ```bash bun x hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --parameters '{"CounterModule":{"initialValue":"100"}}' ``` ## Next Steps # Get Started (/docs/builders/smart-contracts/hardhat/get-started) import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; [Hardhat](https://hardhat.org) is a flexible and extensible development environment for Ethereum software. It helps you write, test, debug, and deploy your smart contracts with ease. ## Prerequisites * [Node.js](https://nodejs.org/) v22 or later * A package manager like npm, yarn, pnpm, or bun * A wallet with testnet ETH from the [RISE Faucet](https://faucet.testnet.riselabs.xyz/) ## Create a Project ### Initialize Hardhat Project Create a new directory and initialize a Hardhat project: ```bash mkdir my-rise-project cd my-rise-project ``` Initialize Hardhat with the setup wizard: ```bash npx hardhat --init ``` ```bash yarn dlx hardhat --init ``` ```bash pnpm dlx hardhat --init ``` ```bash bun x hardhat --init ``` When prompted, select: 1. **Version**: "Hardhat 3 Beta" 2. **Path**: current directory (`.`) 3. **Project type**: "A minimal Hardhat project" 4. **Install dependencies**: true This will create a complete project structure with all necessary dependencies. ### Configure for RISE Update your `hardhat.config.ts` to add the RISE Testnet: ```typescript title="hardhat.config.ts" {11-16} import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem"; import { defineConfig } from "hardhat/config"; export default defineConfig({ plugins: [hardhatToolboxViemPlugin], solidity: { version: "0.8.30", }, networks: { riseTestnet: { type: "http", url: "https://testnet.riselabs.xyz", accounts: [""], chainId: 11155931 } } }); ``` Never commit private keys directly in config files. In the next step, you'll learn how to use Configuration Variables to keep this secure. ### Secure Your Private Key Use Hardhat's Configuration Variables (keystore) to store your private key securely: ```bash npx hardhat keystore set RISE_PRIVATE_KEY ``` ```bash yarn hardhat keystore set RISE_PRIVATE_KEY ``` ```bash pnpm exec hardhat keystore set RISE_PRIVATE_KEY ``` ```bash bun x hardhat keystore set RISE_PRIVATE_KEY ``` Then update your config to use the variable: ```typescript title="hardhat.config.ts" {2,13} import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem"; import { configVariable, defineConfig } from "hardhat/config"; export default defineConfig({ plugins: [hardhatToolboxViemPlugin], solidity: { version: "0.8.30", }, networks: { riseTestnet: { type: "http", url: "https://testnet.riselabs.xyz", accounts: [configVariable("RISE_PRIVATE_KEY")], chainId: 11155931 } } }); ``` If you prefer to use a `.env` file with the `dotenv` package instead of Hardhat's keystore, you can do so, but **this is not recommended** as it's less secure. Make sure to never commit your `.env` file to version control by adding it to `.gitignore`. 1. Create a `.env` file: `PRIVATE_KEY=your_private_key_here` 2. Install dotenv: `npm install dotenv` 3. Add to the top of your config: `require("dotenv").config();` 4. Update accounts: `accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []` ### Create a Smart Contract Make sure you're in your Hardhat project's root directory. Then create a `contracts` directory and add a simple Counter contract: ```bash mkdir contracts ``` Create `contracts/Counter.sol`: ```solidity title="contracts/Counter.sol" // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.30; contract Counter { uint256 public number; function setNumber(uint256 newNumber) public { number = newNumber; } function increment() public { number++; } } ``` ### Compile the Contract Compile your contract to verify everything is working: ```bash npx hardhat compile ``` ```bash yarn hardhat compile ``` ```bash pnpm exec hardhat compile ``` ```bash bun x hardhat compile ``` This generates artifacts in the `artifacts/` directory. ## Next Steps # Hardhat (/docs/builders/smart-contracts/hardhat) [Hardhat](https://hardhat.org) is a development environment for Ethereum that helps you compile, deploy, test, and debug your smart contracts. RISE is fully EVM compatible, so you can use Hardhat with minimal configuration changes. ## Quick Links # Testing Contracts (/docs/builders/smart-contracts/hardhat/testing) import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Hardhat includes a built-in testing framework using Mocha and Chai. Write tests to ensure your contracts work correctly before deploying to RISE. ## Write Tests ### Create Test File Make sure you're in your project's root directory and create a new directory called `test`. ```bash mkdir test ``` Create a test file in the `test/` directory using ESM syntax: ```typescript title="test/Counter.ts" import hre from "hardhat"; import { expect } from "chai"; const { ethers } = await hre.network.connect(); describe("Counter", function () { it("should start with 0", async function () { const counter = await ethers.deployContract("Counter"); expect(await counter.number()).to.equal(0n); }); it("should increment", async function () { const counter = await ethers.deployContract("Counter"); await counter.increment(); expect(await counter.number()).to.equal(1n); }); it("should set number", async function () { const counter = await ethers.deployContract("Counter"); await counter.setNumber(42n); expect(await counter.number()).to.equal(42n); }); }); ``` Note: In Hardhat 3, you explicitly create network connections with `hre.network.connect()`. ### Run Tests Execute your tests: ```bash npx hardhat test ``` ```bash yarn hardhat test ``` ```bash pnpm exec hardhat test ``` ```bash bun x hardhat test ``` Output: ``` Counter ✔ should start with 0 ✔ should increment ✔ should set number 3 passing ``` ## Run Specific Tests Run a single test file: ```bash npx hardhat test test/Counter.ts ``` ```bash yarn hardhat test test/Counter.ts ``` ```bash pnpm exec hardhat test test/Counter.ts ``` ```bash bun x hardhat test test/Counter.ts ``` Run tests matching a pattern: ```bash npx hardhat test --grep "increment" ``` ```bash yarn hardhat test --grep "increment" ``` ```bash pnpm exec hardhat test --grep "increment" ``` ```bash bun x hardhat test --grep "increment" ``` ## Next Steps # Verifying Contracts (/docs/builders/smart-contracts/hardhat/verifying) import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; Verify your deployed contracts on the RISE Testnet Explorer so users can view and interact with your contract's source code. ## Prerequisites * Contract deployed to RISE (see [Deploying](/docs/builders/smart-contracts/hardhat/deploying)) * Contract address from deployment ## Verify with Hardhat Ignition ### Configure Verification Add the RISE Explorer configuration to your `hardhat.config.ts`: ```typescript title="hardhat.config.ts" {3,17-24} import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem"; import { configVariable, defineConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-verify"; export default defineConfig({ plugins: [hardhatToolboxViemPlugin], solidity: { version: "0.8.30", }, networks: { rise: { type: "http", url: "https://testnet.riselabs.xyz", accounts: [configVariable("RISE_PRIVATE_KEY")], chainId: 11155931 } }, verify: { blockscout: { networks: { rise: "https://explorer.testnet.riselabs.xyz/api" } } } }); ``` ### Verify with Ignition If you deployed with Hardhat Ignition, simply add the `--verify` flag: ```bash npx hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --verify ``` ```bash yarn hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --verify ``` ```bash pnpm exec hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --verify ``` ```bash bun x hardhat ignition deploy ignition/modules/Counter.ts --network riseTestnet --verify ``` Since you already deployed the contract, Ignition won't re-deploy it. It will only submit the source code for verification. ### Check Verification You'll see output indicating successful verification with a link to view the contract on the RISE Explorer. Visit [explorer.testnet.riselabs.xyz](https://explorer.testnet.riselabs.xyz) and search for your contract address to see the verified source code. ## Troubleshooting ### Already Verified If you see "Already Verified", the contract source code has already been submitted. No further action needed. ### Wrong Constructor Arguments If verification fails, double-check your constructor arguments match exactly what was used during deployment. ### Compiler Version Mismatch Make sure the Solidity version in your `hardhat.config` matches the version used in your contract. ## Next Steps Your verified contract is now publicly viewable on the RISE Explorer. Users can: * Read the source code * Interact with contract functions directly through the explorer * View contract events and transactions # Cancel Order (/docs/risex/api/examples/cancel-order) # Cancel Order Cancel an existing order. **Endpoint:** `POST /v1/orders/cancel` **Prerequisites:** You must have registered a signer first (see [Register Signer](/docs/risex/api/register-signer)). **Example:** ```typescript import axios from "axios"; import { apiClient } from "./risex-core"; import { createPermitParams } from "./create-permit-params"; import { encodeAbiParameters, type Hex } from "viem"; import 'dotenv/config'; const cancelOrder = async ( marketId: number, orderId: string, signingKey: `0x${string}`, // From registerSigner signerAddress: string, // From registerSigner account: string, ) => { try { // Contract addresses const perpContractAddress = '0x68cAcD54a8c93A3186BF50bE6b78B761F728E1b4' as `0x${string}`; const authContractAddress = '0x8d8708f9D87ef522c1f99DD579BF6A051e34C28E' as `0x${string}`; // Encode cancel data (32 bytes) // Binary layout: (marketId << 192) | orderId // This packs marketId in the first 8 bytes and orderId in the remaining 24 bytes const cancelData = (BigInt(marketId) << 192n) | BigInt(orderId); const cancelDataHex = '0x' + cancelData.toString(16).padStart(64, '0') as Hex; const encodedData = encodeAbiParameters( [{ name: 'cancelData', type: 'bytes32' }], [cancelDataHex] ) as Hex; // Create permit params with signature const permitParams = await createPermitParams( encodedData, signingKey, account as `0x${string}`, signerAddress as `0x${string}`, perpContractAddress, authContractAddress, ); const response = await apiClient.post('/v1/orders/cancel', { market_id: marketId.toString(), order_id: orderId, permit_params: { account: permitParams.account, signer: permitParams.signer, deadline: permitParams.deadline, signature: permitParams.signature, nonce: permitParams.nonce, }, }); console.log('Order cancelled successfully!'); console.log('Response:', response.data); return response.data; } catch (error) { if (axios.isAxiosError(error)) { console.error('Cancel order failed:', error.response?.data?.message || error.message); if (error.response?.data) { console.error('Error details:', JSON.stringify(error.response.data, null, 2)); } } else { console.error('Cancel order failed:', (error as Error).message); } throw error; } }; export { cancelOrder }; // Example usage: const signingKey = process.env.SIGNING_KEY as `0x${string}`; const signerAddress = '0x...'; // Your signer address from registerSigner const account = '0x...'; // Your main wallet address await cancelOrder( 1, // BTC market '12345678', // Order ID from place-order response signingKey, signerAddress, account ); ``` # Deposit USDC (/docs/risex/api/examples/deposit) # Deposit USDC Deposit USDC into the exchange for trading. **Endpoint:** `POST /v1/account/deposit` **Example:** ```typescript import axios from "axios"; import { apiClient } from "./risex-core"; const depositUSDC = async ( account: string, amount: string, // Amount in plain decimal (e.g., "100" for 100 USDC) ) => { try { const response = await apiClient.post('/v1/account/deposit', { account: account, amount: amount, }); const { data } = response.data; console.log('Deposit successful!'); console.log('Transaction Hash:', data.transaction_hash); console.log('Block Number:', data.block_number); return data; } catch (error) { if (axios.isAxiosError(error)) { console.error('Deposit failed:', error.response?.data?.message || error.message); } else { console.error('Deposit failed:', (error as Error).message); } throw error; } }; // Example usage: // await depositUSDC( // '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', // Your account address // '100' // Deposit 100 USDC // ); ``` ## Important Notes * **Amount Format**: The amount should be in plain decimal format (e.g., "100" for 100 USDC), not with 18 decimals * **Gas Sponsored**: Deposit transactions are gas-sponsored by the API service * **Balance Update**: After deposit, your balance will be updated in the exchange and can be used for trading # Place Order (/docs/risex/api/examples/place-order) # Place Order Place a new trading order. **Endpoint:** `POST /v1/orders/place` **Prerequisites:** You must have registered a signer first (see [Register Signer](/docs/risex/api/register-signer)). **Example:** ```typescript import axios from "axios"; import { apiClient } from "./risex-core"; import { MarketsId, OrderSide, STPMode, OrderType, TimeInForce, encodePlaceOrderData } from "./types"; import { createPermitParams } from "./create-permit-params"; const placeOrder = async ( orderParams: { market_id: MarketsId; size: string; price: string; side: OrderSide; stp_mode: STPMode; order_type: OrderType; post_only: boolean; reduce_only: boolean; tif: TimeInForce; expiry: number; }, signingKey: `0x${string}`, // From registerSigner signerAddress: string, // From registerSigner account: string, ) => { try { // Contract addresses const perpContractAddress = '0x68cAcD54a8c93A3186BF50bE6b78B761F728E1b4' as `0x${string}`; const authContractAddress = '0x8d8708f9D87ef522c1f99DD579BF6A051e34C28E' as `0x${string}`; // Encode order data const encodedData = encodePlaceOrderData({ marketId: orderParams.market_id.toString(), size: BigInt(orderParams.size), price: BigInt(orderParams.price), side: orderParams.side, stpMode: orderParams.stp_mode, orderType: orderParams.order_type, postOnly: orderParams.post_only, reduceOnly: orderParams.reduce_only, timeInForce: orderParams.tif, expiry: orderParams.expiry, }); // Create permit params with signature const permitParams = await createPermitParams( encodedData, signingKey, account as `0x${string}`, signerAddress as `0x${string}`, perpContractAddress, authContractAddress, ); const response = await apiClient.post('/v1/orders/place', { order_params: { market_id: orderParams.market_id, size: orderParams.size, price: orderParams.price, side: orderParams.side, stp_mode: orderParams.stp_mode, order_type: orderParams.order_type, post_only: orderParams.post_only, reduce_only: orderParams.reduce_only, tif: orderParams.tif, expiry: orderParams.expiry, }, permit_params: { account: permitParams.account, signer: permitParams.signer, deadline: permitParams.deadline, signature: permitParams.signature, nonce: permitParams.nonce, }, }); const { data } = response.data; console.log('Order placed successfully!'); console.log('Transaction Hash:', data.transaction_hash); console.log('Order ID:', data.order_id); return data; } catch (error) { if (axios.isAxiosError(error)) { console.error('Order failed:', error.response?.data?.message || error.message); } else { console.error('Order failed:', (error as Error).message); } throw error; } }; // Example usage: // await placeOrder( // { // market_id: MarketsId.BTC, // size: '1000000000000000', // 0.001 BTC // price: '87467000000000000000000', // 87,467 USDC (18 decimals) // side: OrderSide.Long, // stp_mode: STPMode.None, // order_type: OrderType.Market, // post_only: false, // reduce_only: false, // tif: TimeInForce.ImmediateOrCancel, // expiry: 0, // }, // signingKey, // signerAddress, // account // ); ``` # Update Leverage (/docs/risex/api/examples/update-leverage) # Update Leverage Update account leverage for a specific market. **Endpoint:** `POST /v1/account/leverage` **Prerequisites:** You must have registered a signer first (see [Register Signer](/docs/risex/api/register-signer)). **Example:** ```typescript import axios from "axios"; import { apiClient } from "./risex-core"; import { createPermitParams } from "./create-permit-params"; import { encodeAbiParameters, type Hex } from "viem"; const updateLeverage = async ( marketId: number, leverageDecimal: string, // e.g., "10" for 10x leverage signingKey: `0x${string}`, // From registerSigner signerAddress: string, // From registerSigner account: string, ) => { try { // Contract addresses const perpContractAddress = '0x68cAcD54a8c93A3186BF50bE6b78B761F728E1b4' as `0x${string}`; const authContractAddress = '0x8d8708f9D87ef522c1f99DD579BF6A051e34C28E' as `0x${string}`; // Encode leverage data (NO 18 decimals - use plain value) // keccak256(abi.encode(uint256(marketId), uint128(leverage))) const encodedData = encodeAbiParameters( [ { name: 'marketId', type: 'uint256' }, { name: 'leverage', type: 'uint128' } ], [BigInt(marketId), BigInt(leverageDecimal)] ) as Hex; // Create permit params with signature const permitParams = await createPermitParams( encodedData, signingKey, account as `0x${string}`, signerAddress as `0x${string}`, perpContractAddress, authContractAddress, ); const response = await apiClient.post('/v1/account/leverage', { market_id: marketId.toString(), leverage: leverageDecimal, // API wants plain decimal string permit_params: { account: permitParams.account, signer: permitParams.signer, deadline: permitParams.deadline, signature: permitParams.signature, nonce: permitParams.nonce, }, }); console.log('Leverage updated successfully!'); console.log('Transaction Hash:', response.data.transaction_hash); return response.data; } catch (error) { if (axios.isAxiosError(error)) { console.error('Update leverage failed:', error.response?.data?.message || error.message); } else { console.error('Update leverage failed:', (error as Error).message); } throw error; } }; // Example usage: // await updateLeverage( // 1, // BTC market // '10', // 10x leverage (plain decimal, not 18 decimals!) // signingKey, // signerAddress, // account // ); ``` # Authorization (/docs/risex/contracts/contract-interface/authorization) ## Contract Functions ### Register Signer **Description** Registers a signer (session key) to sign on behalf of an account. The signer must be an EOA, while the account can be either an EOA or a smart contract account. Both the account and signer must provide EIP-712 typed data signatures. ```solidity function registerSigner( address account, address signer, string memory message, uint256 nonce, uint40 expiration, bytes memory accountSignature, bytes memory signerSignature ) external; ``` **Parameters** * `account`: The account address that will authorize the signer * `signer`: The signer address (must be an EOA) that will sign on behalf of the account * `message`: A message string for the account signature * `nonce`: A unique nonce for both account and signer to prevent replay attacks * `expiration`: The expiration timestamp (uint40) when the session key expires * `accountSignature`: EIP-712 signature from the account authorizing the signer * `signerSignature`: EIP-712 signature from the signer confirming authorization **Example** ```solidity // Prepare registration data address account = 0x...; address signer = 0x...; string memory message = "Authorize signer"; uint256 nonce = 123; uint40 expiration = uint40(block.timestamp + 30 days); bytes32 registerHash = _hashTypedDataV4( keccak256( abi.encode( REGISTER_SIGNER_TYPEHASH, // "RegisterSigner(address signer,string message,uint40 expiration,uint256 nonce)" signer, keccak256(abi.encodePacked(message)), expiration, nonce ) ) ); bytes memory accountSignature = _signTypedDataHash(accountPrivateKey, registerHash); bytes32 signKeyHash = _hashTypedDataV4( keccak256( abi.encode( VERIFY_SIGNER_TYPEHASH, // "VerifySigner(address account,uint256 nonce)" account, nonce ) ) ); bytes memory signerSignature = _signTypedDataHash(signerPrivateKey, signKeyHash); // Register the signer risExAuthorization.registerSigner( account, signer, message, nonce, expiration, accountSignature, signerSignature ); ``` *** ### Revoke Signer **Description** Revokes a signer's authorization to sign on behalf of an account. The signer must currently be authorized. The account must provide an EIP-712 typed data signature. ```solidity function revokeSigner( address account, address signer, uint256 nonce, bytes memory accountSignature ) external; ``` **Parameters** * `account`: The account address that authorized the signer * `signer`: The signer address to revoke * `nonce`: A unique nonce to prevent replay attacks * `accountSignature`: EIP-712 signature from the account authorizing the revocation **Example** ```solidity // Prepare revocation data address account = 0x...; address signer = 0x...; uint256 nonce = 456; bytes32 revokeHash = _hashTypedDataV4( keccak256( abi.encode( REVOKE_SIGNER_TYPEHASH, // "RevokeSigner(address signer,uint256 nonce)" signer, nonce ) ) ); bytes memory accountSignature = _signTypedDataHash(accountPrivateKey, revokeHash); // Revoke the signer risExAuthorization.revokeSigner(account, signer, nonce, accountSignature); ``` # Perp (/docs/risex/contracts/contract-interface/perp) ### Deposit **Description** Deposits collateral tokens into the specified account. The caller must have approved the PerpsManager contract to spend the tokens. ```solidity function deposit(address to, address token, uint256 amount) external; ``` **Example** ```solidity // Approve tokens first IERC20(usdc).approve(perpsManager, amount); // Deposit USDC perpsManager.deposit(account, usdc, amount); ``` *** ### Withdraw **Description** Withdraws collateral tokens from the caller's account to the specified address. The withdrawable amount is limited by the account's free margin balance. ```solidity function withdraw(address to, address token, uint256 amount) external; ``` **Parameters** * `to`: The address to withdraw tokens to * `token`: The address of the collateral token to withdraw * `amount`: The amount of tokens to withdraw (in token's native decimals) **Example** ```solidity // Get the withdrawable amount uint256 withdrawableAmount = perpsManager.getWithdrawableAmount(account, usdc); // Withdraw USDC to the recipient perpsManager.withdraw(recipient, usdc, withdrawableAmount); ``` *** ### Place Order **Description** Places a trading order (market or limit) in the specified market. The order data is encoded in a packed binary format for gas efficiency. ```solidity function placeOrder(bytes calldata placeOrderData) external returns (uint256 orderId); ``` **Parameters** * `placeOrderData`: Encoded order data (47 bytes) containing: * `marketId` (uint64, 8 bytes): The market ID * `size` (uint128, 16 bytes): The order size (in 18 decimals) * `price` (uint128, 16 bytes): The limit price (0 for market orders) (in 18 decimals) * `flags` (uint8, 1 byte): Packed flags: * bit 0: side (0 = Buy, 1 = Sell) * bit 1: postOnly * bit 2: reduceOnly * bits 3-4: stpMode (0 = ExpireMaker, 1 = ExpireTaker, 2 = ExpireBoth, 3 = None) * `orderType` (uint8, 1 byte): OrderType enum (0 = Market, 1 = Limit) * `timeInForce` (uint8, 1 byte): TimeInForce enum (0 = GoodTillCancelled, 1 = GoodTillTime, 2 = FillOrKill, 3 = ImmediateOrCancel) * `expiry` (uint32, 4 bytes): Expiry timestamp (for GoodTillTime orders) (0 for other order types) **Example** ```solidity // Encode place order data uint64 marketId = 1; // BTC uint128 size = 1e18; // 1 BTC uint128 price = 100_000e18; // $100,000 uint8 side = 0; // Buy uint8 postOnly = 0; // False uint8 reduceOnly = 0; // False uint8 stpMode = 0; // ExpireMaker uint8 flags = (side << 0) | (postOnly << 1) | (reduceOnly << 2) | (stpMode << 3); bytes memory placeOrderData = abi.encodePacked(marketId, size, price, flags, OrderType.Limit, TimeInForce.GoodTillCancelled, 0); uint256 orderId = perpsManager.placeOrder(placeOrderData); ``` *** ### Place Order With Permit **Description** Places an order with EIP-712 typed data signature, allowing gasless approvals for trading. The signer must be registered in the `RISExAuthorization` contract. ```solidity function placeOrderWithPermit( bytes calldata placeOrderData, InputTypes.PermitParams calldata permit ) external returns (uint256 orderId); ``` **Parameters** * `placeOrderData`: Encoded order data (same as `placeOrder`) * `permit`: Permit parameters: * `account`: The account address * `signer`: The signer address * `deadline`: Signature expiry timestamp * `signature`: EIP-712 signature * `nonce`: The unique nonce for the signature to prevent replay attacks **Example** ```solidity // Encode place order data uint64 marketId = 1; // BTC uint128 size = 1e18; // 1 BTC uint128 price = 100_000e18; // $100,000 uint8 side = 0; // Buy uint8 postOnly = 0; // False uint8 reduceOnly = 0; // False uint8 stpMode = 0; // ExpireMaker uint8 flags = (side << 0) | (postOnly << 1) | (reduceOnly << 2) | (stpMode << 3); bytes memory placeOrderData = abi.encodePacked(marketId, size, price, flags, OrderType.Limit, TimeInForce.GoodTillCancelled, 0); // Prepare permit parameters uint256 nonce = 123; uint256 deadline = block.timestamp + 1 hours; bytes32 verifySignatureHash = keccak256( abi.encode( VERIFY_SIGNATURE_TYPEHASH, // "VerifySignature(address account,address target,bytes32 hash,uint256 nonce,uint256 deadline)" account, address(perpsManager), keccak256(placeOrderData), nonce, deadline ) ); bytes memory permitSignature = _signTypedDataHash(signerPrivateKey, verifySignatureHash); InputTypes.PermitParams memory permit = InputTypes.PermitParams({ account: account, signer: signer, deadline: deadline, signature: permitSignature, nonce: nonce }); uint256 orderId = perpsManager.placeOrderWithPermit(placeOrderData, permit); ``` *** ### Cancel Order **Description** Cancels an open limit order in the specified market. ```solidity function cancelOrder(bytes32 cancelOrderData) external; ``` **Parameters** * `cancelOrderData`: Encoded cancel order data (32 bytes) containing: * `marketId` (uint64, 8 bytes): The market ID * `orderId` (uint192, 24 bytes): The order ID to cancel **Example** ```solidity bytes32 cancelOrderData = bytes32(abi.encodePacked( uint64(marketId), uint192(orderId) )); perpsManager.cancelOrder(cancelOrderData); ``` *** ### Cancel Order With Permit **Description** Cancels an order with EIP-712 typed data signature. The signer must be registered in the `RISExAuthorization` contract. ```solidity function cancelOrderWithPermit( bytes32 cancelOrderData, InputTypes.PermitParams calldata permit ) external; ``` **Parameters** * `cancelOrderData`: Encoded cancel order data (same as `cancelOrder`) * `permit`: Permit parameters (same as `placeOrderWithPermit`) **Example** ```solidity // Encode cancel order data bytes32 cancelOrderData = bytes32(abi.encodePacked( uint64(marketId), uint192(orderId) )); // Prepare permit parameters uint256 nonce = 456; uint256 deadline = block.timestamp + 1 hours; bytes32 verifySignatureHash = keccak256( abi.encode( VERIFY_SIGNATURE_TYPEHASH, // "VerifySignature(address account,address target,bytes32 hash,uint256 nonce,uint256 deadline)" account, address(perpsManager), keccak256(cancelOrderData), nonce, deadline ) ); bytes memory permitSignature = _signTypedDataHash(signerPrivateKey, verifySignatureHash); InputTypes.PermitParams memory permit = InputTypes.PermitParams({ account: account, signer: signer, deadline: deadline, signature: permitSignature, nonce: nonce }); perpsManager.cancelOrderWithPermit(cancelOrderData, permit); ``` *** ### Update Margin Mode **Description** Updates the margin mode (Cross or Isolated) for an account in a specific market. ```solidity function updateMarginMode(InputTypes.UpdateMarginModeParams calldata params) external; ``` **Parameters** * `params`: UpdateMarginModeParams struct: * `marketId`: The market ID * `marginMode`: The new margin mode (0 = Cross, 1 = Isolated) **Example** ```solidity InputTypes.UpdateMarginModeParams memory params = InputTypes.UpdateMarginModeParams({ marketId: marketId, marginMode: Market.MarginMode.Isolated }); perpsManager.updateMarginMode(params); ``` *** ### Update Margin Mode With Permit **Description** Updates margin mode with EIP-712 typed data signature. The signer must be registered in the `RISExAuthorization` contract. ```solidity function updateMarginModeWithPermit( InputTypes.UpdateMarginModeParams calldata params, InputTypes.PermitParams calldata permit ) external; ``` **Parameters** * `params`: UpdateMarginModeParams struct * `permit`: Permit parameters **Example** ```solidity // Encode update margin mode params uint64 marketId = 1; // BTC uint8 marginMode = 1; // Isolated InputTypes.UpdateMarginModeParams memory params = InputTypes.UpdateMarginModeParams({ marketId: marketId, marginMode: Market.MarginMode(marginMode) }); // Prepare permit parameters uint256 nonce = 456; uint256 deadline = block.timestamp + 1 hours; bytes32 verifySignatureHash = keccak256( abi.encode( VERIFY_SIGNATURE_TYPEHASH, // "VerifySignature(address account,address target,bytes32 hash,uint256 nonce,uint256 deadline)" account, address(perpsManager), keccak256(abi.encode(params)), nonce, deadline ) ); bytes memory permitSignature = _signTypedDataHash(signerPrivateKey, verifySignatureHash); InputTypes.PermitParams memory permit = InputTypes.PermitParams({ account: account, signer: signer, deadline: deadline, signature: permitSignature, nonce: nonce }); perpsManager.updateMarginModeWithPermit(params, permit); ``` *** ### Update Leverage **Description** Updates the leverage for an account in a specific market. Leverage determines the maximum position size relative to margin. ```solidity function updateLeverage(InputTypes.UpdateLeverageParams calldata params) external; ``` **Parameters** * `params`: UpdateLeverageParams struct: * `marketId`: The market ID * `leverage`: The new leverage (as a multiplier, e.g., 10 for 10x) **Example** ```solidity InputTypes.UpdateLeverageParams memory params = InputTypes.UpdateLeverageParams({ marketId: marketId, leverage: 20 // 20x leverage }); perpsManager.updateLeverage(params); ``` *** ### Update Leverage With Permit **Description** Updates leverage with EIP-712 typed data signature. The signer must be registered in the `RISExAuthorization` contract. ```solidity function updateLeverageWithPermit( InputTypes.UpdateLeverageParams calldata params, InputTypes.PermitParams calldata permit ) external; ``` **Parameters** * `params`: UpdateLeverageParams struct * `permit`: Permit parameters **Example** ```solidity // Encode update leverage params uint64 marketId = 1; // BTC uint128 leverage = 10e18; // 10x leverage InputTypes.UpdateLeverageParams memory params = InputTypes.UpdateLeverageParams({ marketId: marketId, leverage: leverage }); // Prepare permit parameters uint256 nonce = 456; uint256 deadline = block.timestamp + 1 hours; bytes32 verifySignatureHash = keccak256( abi.encode( VERIFY_SIGNATURE_TYPEHASH, // "VerifySignature(address account,address target,bytes32 hash,uint256 nonce,uint256 deadline)" account, address(perpsManager), keccak256(abi.encode(params)), nonce, deadline ) ); bytes memory permitSignature = _signTypedDataHash(signerPrivateKey, verifySignatureHash); InputTypes.PermitParams memory permit = InputTypes.PermitParams({ account: account, signer: signer, deadline: deadline, signature: permitSignature, nonce: nonce }); perpsManager.updateLeverageWithPermit(params, permit); ``` *** ### Update Isolated Position Margin Balance **Description** Adds or removes margin from an isolated position. Positive amount adds margin, negative amount removes margin. ```solidity function updateIsolatedPositionMarginBalance( InputTypes.UpdateIsolatedPositionMarginBalanceParams calldata params ) external; ``` **Parameters** * `params`: UpdateIsolatedPositionMarginBalanceParams struct: * `marketId`: The market ID * `amount`: The delta amount to add (if positive) or remove (if negative), in USDC with 18 decimals **Example** ```solidity // Add 100 USDC to isolated position InputTypes.UpdateIsolatedPositionMarginBalanceParams memory params = InputTypes.UpdateIsolatedPositionMarginBalanceParams({ marketId: marketId, amount: 100e18 // Positive to add }); perpsManager.updateIsolatedPositionMarginBalance(params); // Remove 50 USDC from isolated position params.amount = -50e18; // Negative to remove perpsManager.updateIsolatedPositionMarginBalance(params); ``` *** ### Update Isolated Position Margin Balance With Permit **Description** Updates isolated position margin balance with EIP-712 typed data signature. The signer must be registered in the `RISExAuthorization` contract. ```solidity function updateIsolatedPositionMarginBalanceWithPermit( InputTypes.UpdateIsolatedPositionMarginBalanceParams calldata params, InputTypes.PermitParams calldata permit ) external; ``` **Parameters** * `params`: UpdateIsolatedPositionMarginBalanceParams struct * `permit`: Permit parameters **Example** ```solidity // Encode update isolated position margin balance params uint64 marketId = 1; // BTC int256 amount = 1000e18; // 1000 USDC InputTypes.UpdateIsolatedPositionMarginBalanceParams memory params = InputTypes.UpdateIsolatedPositionMarginBalanceParams({ marketId: marketId, amount: amount }); // Prepare permit parameters uint256 nonce = 456; uint256 deadline = block.timestamp + 1 hours; bytes32 verifySignatureHash = keccak256( abi.encode( VERIFY_SIGNATURE_TYPEHASH, // "VerifySignature(address account,address target,bytes32 hash,uint256 nonce,uint256 deadline)" account, address(perpsManager), keccak256(abi.encode(params)), nonce, deadline ) ); bytes memory permitSignature = _signTypedDataHash(signerPrivateKey, verifySignatureHash); InputTypes.PermitParams memory permit = InputTypes.PermitParams({ account: account, signer: signer, deadline: deadline, signature: permitSignature, nonce: nonce }); perpsManager.updateIsolatedPositionMarginBalanceWithPermit(params, permit); ``` *** ## View Functions ### Get Balance **Description** Returns the collateral balance for a specific token for an account. ```solidity function getBalance(address account, address token) external view returns (int256); ``` **Parameters** * `account`: The account address * `token`: The token address **Returns** * `int256`: The balance (can be negative in edge cases) **Example** ```solidity int256 balance = perpsManager.getBalance(account, usdc); ``` *** ### Get Position **Description** Returns the position for an account in a market. ```solidity function getPosition(uint256 marketId, address account) external view returns (Market.Position memory); ``` **Parameters** * `marketId`: The market ID * `account`: The account address **Returns** * `Market.Position`: The position struct containing: * `size`: Position size (positive for long, negative for short) * `quoteAmount`: Quote amount * `marginMode`: Current margin mode * `side`: Position side (Buy = long, Sell = short) * `isolatedUsdcBalance`: Isolated margin balance (if isolated mode) * `lastFundingPayment`: Last funding payment * `leverage`: Current leverage **Example** ```solidity Market.Position memory position = perpsManager.getPosition(marketId, account); ``` *** ### Get Leverage **Description** Returns the leverage for an account in a market. ```solidity function getLeverage(uint256 marketId, address account) external view returns (uint256); ``` **Parameters** * `marketId`: The market ID * `account`: The account address **Returns** * `uint256`: The leverage multiplier **Example** ```solidity uint256 leverage = perpsManager.getLeverage(marketId, account); ``` *** ### Get Market Config **Description** Returns the configuration for a market. ```solidity function getMarketConfig(uint256 marketId) external view returns (Market.Config memory); ``` **Parameters** * `marketId`: The market ID **Returns** * `Market.Config`: The market configuration containing: * `name`: Market name * `quote`: Quote token address * `stepSize`: Minimum order size step * `stepPrice`: Minimum price step * `maintenanceMarginFactor`: Maintenance margin factor * `maxLeverage`: Maximum allowed leverage * `minOrderSize`: Minimum order size * `unlocked`: Whether the market is unlocked **Example** ```solidity Market.Config memory config = perpsManager.getMarketConfig(marketId); ``` *** ### Get Best Price **Description** Returns the best bid or ask price in a market. ```solidity function getBestPrice(uint256 marketId, OrderSide side) external view returns (uint256); ``` **Parameters** * `marketId`: The market ID * `side`: OrderSide (Buy for bid, Sell for ask) **Returns** * `uint256`: The best price, or 0 if no orders exist **Example** ```solidity uint256 bestBid = perpsManager.getBestPrice(marketId, OrderSide.Buy); uint256 bestAsk = perpsManager.getBestPrice(marketId, OrderSide.Sell); ``` *** ### Get Order **Description** Returns an order by market ID and order ID. ```solidity function getOrder(uint256 marketId, uint256 orderId) external view returns (Order memory); ``` **Parameters** * `marketId`: The market ID * `orderId`: The order ID **Returns** * `Order`: The order struct **Example** ```solidity Order memory order = perpsManager.getOrder(marketId, orderId); ``` *** ### Get Open Orders **Description** Returns a paginated list of open order IDs for an account in a market. ```solidity function getOpenOrders( uint256 marketId, address account, uint256 startIndex, uint256 limit ) external view returns (uint256[] memory); ``` **Parameters** * `marketId`: The market ID * `account`: The account address * `startIndex`: The starting index (0-based) * `limit`: Maximum number of orders to return **Returns** * `uint256[]`: Array of order IDs **Example** ```solidity uint256[] memory orderIds = perpsManager.getOpenOrders(marketId, account, 0, 10); ``` *** ### Get Withdrawable Amount **Description** Returns the maximum withdrawable amount for a specific token for an account, considering margin requirements. ```solidity function getWithdrawableAmount(address account, address token) external view returns (uint256); ``` **Parameters** * `account`: The account address * `token`: The token address **Returns** * `uint256`: The withdrawable amount (in token's native decimals) **Example** ```solidity uint256 withdrawable = perpsManager.getWithdrawableAmount(account, usdc); ``` *** ### Get Account Equity **Description** Returns the total account equity in USDC, including all positions and collateral. ```solidity function getAccountEquity(address account) external view returns (int256); ``` **Parameters** * `account`: The account address **Returns** * `int256`: The account equity (can be negative if underwater) **Example** ```solidity int256 equity = perpsManager.getAccountEquity(account); ``` *** ### Get Cross Margin Balance **Description** Returns the cross margin balance for an account. ```solidity function getCrossMarginBalance(address account) external view returns (int256); ``` **Parameters** * `account`: The account address **Returns** * `int256`: The cross margin balance **Example** ```solidity int256 crossMargin = perpsManager.getCrossMarginBalance(account); ``` *** ### Get Free Cross Margin Balance **Description** Returns the free (available for withdrawal) cross margin balance for an account. ```solidity function getFreeCrossMarginBalance(address account) external view returns (uint256); ``` **Parameters** * `account`: The account address **Returns** * `uint256`: The free cross margin balance **Example** ```solidity uint256 freeMargin = perpsManager.getFreeCrossMarginBalance(account); ```