| section | developers | ||
|---|---|---|---|
| date | Last Modified | ||
| title | Developer Quickstart | ||
| lang | en | ||
| permalink | developers/developer-quickstart | ||
| excerpt | Scroll Developer Quickstart helps you acquire testnet Ether, configure your network, and access all of your favorite tooling | ||
| whatsnext |
|
import Aside from "../../../../components/Aside.astro" import ClickToZoom from "../../../../components/ClickToZoom.astro" import networkSelection from "./_images/mmNetworkSelection.png" import injectedProviderMM from "./_images/injectedProviderMM.png" import ToggleElement from "../../../../components/ToggleElement.astro" import wagmiDemo from "../../../../assets/images/developers/getting-started/wagmi-demo.png"
Welcome to the Scroll Developer Quickstart. This guide walks you through building a minimal on-chain app, from installing tooling to deploying contracts on Scroll and connecting it to a React frontend.
By the time you're done, you'll have:
- Installed developer tooling.
- Deployed a Counter smart contract on Scroll by using Foundry.
- Created a React frontend (with wagmi and Viem) to read from and write to your contract.
Scroll is a high performance, EVM equivalent zkEVM Layer 2 designed to help developers build secure, low-cost, and engaging applications. Because Scroll is fully bytecode compatible with the EVM, your existing development and testing tools work out of the box, just configure them to use a Scroll RPC provider.
If you run into any issues, please reach out in our Discord.
We'll use Foundry to compile and deploy our Counter contract.
Create a folder for the contract:
# create a new contracts directory
mkdir contracts
cd contracts
# install and update foundry if you haven't
curl -L https://foundry.paradigm.xyz | bash
foundryup
# initialize a fresh Foundry project
forge init --no-gitTo deploy your smart contracts on Scroll, you need two key components:
- An RPC node connection to interact with the Scroll network: https://sepolia-rpc.scroll.io/
- A funded private key to deploy the contract
Let's set up both of these:
.env
SCROLL_SEPOLIA_RPC_URL="https://sepolia-rpc.scroll.io/"
PRIVATE_KEY=<YOUR_PRIVATE_KEY>Also be sure you don't upload this to a public repo by setting up a .gitignore.
.gitignore
.envIf you don't have Sepolia test ETH yet, join our Telegram faucet and send /drop YOUR_ADDRESS to receive Sepolia ETH on Scroll.
With Foundry set up and .env in place, deploy the Counter contract.
In the contracts directory, run:
source .env
forge create src/Counter.sol:Counter \
--rpc-url $SCROLL_SEPOLIA_RPC_URL \
--broadcast \
--private-key $PRIVATE_KEYAfter deployment, you'll see something like:
[⠊] Compiling...
No files changed, compilation skipped
Deployer: 0xbef34f2FCAe62dC3404c3d01AF65a7784c9c4A19
Deployed to: 0xf0e9ceCAE516B2F5Ac297B3857453b07455f817F
Transaction hash: 0x2bca5934ad82ce26332847fdd6a9241b0da0e38e6928f06499ee58ebc967bbdeCopy the address under Deployed to:. You'll need it when configuring the frontend.
We'll build a simple React app (using Vite, wagmi, and Viem) that connects to MetaMask (or another injected provider) and interacts with the Counter contract.
From the root of your project:
cd ..
pnpm create wagmi frontend -t vite-react
cd frontend
pnpm installThis scaffolds a new Vite + React + wagmi template.
Now add the smart contract address you just deployed to the .env.local file.
.env.local
VITE_COUNTER_CONTRACT_ADDRESS=0xYourNewContractAddressCreate a new Counter component on src/components/Counter.tsx that is able to read and write to our smart contract.
src/components/Counter.tsx
import { useAccount, useWriteContract } from 'wagmi'
import { createPublicClient, http } from 'viem'
import { scrollSepolia } from 'viem/chains'
import { useState, useEffect } from 'react'
const COUNTER_CONTRACT_ADDRESS = import.meta.env.VITE_COUNTER_CONTRACT_ADDRESS as `0x${string}`
// Connect your client to Scroll Sepolia
const publicClient = createPublicClient({
chain: scrollSepolia,
transport: http()
})
// Define the ABI so we can interact with the contract from TS
const counterABI = [
{
inputs: [],
name: "increment",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [],
name: "number",
outputs: [{ type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const
// Custom hook for counter functionality
function useCounter() {
const [number, setNumber] = useState<bigint | null>(null)
const { writeContract, isPending: isIncrementing } = useWriteContract()
const fetchNumber = async () => {
try {
const result = await publicClient.readContract({
address: COUNTER_CONTRACT_ADDRESS,
abi: counterABI,
functionName: 'number',
})
setNumber(result)
} catch (error) {
console.error('Error reading contract:', error)
}
}
useEffect(() => {
fetchNumber()
}, [])
const increment = () => {
writeContract({
address: COUNTER_CONTRACT_ADDRESS,
abi: counterABI,
functionName: 'increment',
}, {
onSuccess: (txHash) => {
publicClient.waitForTransactionReceipt({ hash: txHash })
.then(() => fetchNumber())
.catch(console.error)
},
})
}
return {
number,
isIncrementing,
increment,
refreshNumber: fetchNumber
}
}
// Counter Component
export function Counter() {
const account = useAccount()
const { number, isIncrementing, increment } = useCounter()
return (
<div style={{ marginTop: '20px' }}>
<h2>Counter</h2>
<p>Current number: {number?.toString()}</p>
{account.status === 'connected' && (
<button
onClick={increment}
disabled={isIncrementing}
>
{isIncrementing ? 'Incrementing...' : 'Increment'}
</button>
)}
</div>
)
}Now let's add our new component into src/App.tsx.
import { useAccount, useConnect, useDisconnect } from 'wagmi'
// Import our newly created component
import { Counter } from './components/Counter'
function App() {
const account = useAccount()
const { connectors, connect, error } = useConnect()
const { disconnect } = useDisconnect()
return (
<div style={{ padding: '20px' }}>
<div>
<h2>Wallet</h2>
{account.status === 'connected' ? (
<>
<p>Connected: {account.addresses?.[0]}</p>
<button onClick={() => disconnect()}>Disconnect</button>
</>
) : (
<>
{connectors.map((connector) => (
<button
key={connector.uid}
onClick={() => connect({ connector })}
style={{ marginRight: '10px' }}
>
Connect {connector.name}
</button>
))}
{error && <p style={{ color: 'red' }}>{error.message}</p>}
</>
)}
</div>
{/* Use the Counter component in our dApp */}
<Counter />
</div>
)
}
export default AppStart your development server:
pnpm run devOpen your browser at http://localhost:5173 or whatever Vite shows. You should see wallet connection buttons and an increment button. First, click the connect wallet button corresponding to the wallet you installed and then click the increment button to see the number going up.
When you’re ready to switch to mainnet, simply replace the Sepolia RPC URL with the Scroll mainnet RPC URL in your environment: https://rpc.scroll.io/. In your React code, update the Viem client to use scroll instead of scrollSepolia:
import { scroll } from 'viem/chains'
const publicClient = createPublicClient({
chain: scroll,
transport: http(),
})This change tells Viem to point at the live Scroll chain rather than Sepolia. All of your existing contract reads and writes will now go to mainnet.