Fund Me Smart Contract using Typescript on Near (Non-EVM)
Rishabhraghwendra18
Posted on October 29, 2023
Near is a Non-EVM blockchain with 100,000 TPS which is 3x faster, and cheaper than the Non-EVM blockchain Solana!!
On top of that, You can deploy Typescript code as a smart contract on Near!!
Let's build and deploy a fully functional FundMe DApp with Typescript smart contract on Near and learn some new concepts that make Near better than other EVM and non-EVM chains.
Here's a little preview of what we gonna build:
This DApp allows users to donate Near tokens, and allows admins to withdraw the donations from the contract from the blockchain explorer.
So let's go 🚀
Setup the development environment
Setup the development environment using the below template. It's made using Vite React and Near CLI which is much faster than the official Near's npx create-near-app
CLI.
https://github.com/Rishabhraghwendra18/react-near-template
Just click on the top green button to Use this template
which will setup a new repository in your account using the template.
Clone the repository created in your account on your local machine and follow the below instructions:
cd frontend
npm i
In another terminal:
cd contract
npm i
After installing dependencies in both folders, run npm run deploy
in the contract folder to deploy the dummy HelloNear
contract.
On the successful deployment of the contract run, npm run dev
in the frontend folder.
You will see a basic UI running at http://localhost:5173/
in the browser.
Create a testnet wallet on Near
Create a testnet wallet on Near on MyNearWallet. Near will automatically deposit $200 worth of test Near in your wallet so no need for faucets.
Fun Fact: Near doesn't provide a random hash as your wallet account address. It has named account addresses like mine is rishabh.testnet
which is very easy to remember.
Let's now see step by step to build the Typescript smart contract. I will explain to you the meaning of each and every line.
Building the FundMe Contract
Navigate to contract/src/contract.ts
file and remove all the code.
Import dependencies on first line:
import { NearBindgen, near, call, view,UnorderedMap } from 'near-sdk-js';
- NearBindgen: This decorator helps to convert typescript classes to Near smart contracts.
- near: It has lots of built-in near API like getting contract balances, caller account IDs, etc.
- call: Decorator for setter functions in contract.
- view: Decorator for getter functions in the contract.
- UnorderedMap: An iterable hashmap data structure to store the donation amount with the donor account.
Now create a class with NearBindgen decorator:
@NearBindgen({})
class FundMe {
beneficiary: string ="<beneficary-account-id>";
donations= new UnorderedMap<bigint>('d');
}
You may wonder what is 'd'
in the UnorderedMap. It's used as a prefix to store key-value pairs in the memory on the near blockchain. So whenever you have two unorderedmap make sure their prefixes are different.
Let's add donate
function:
@call({payableFunction:true})
donate(){
let donor=near.predecessorAccountId();
let donationAmount: bigint = near.attachedDeposit() as bigint;
let donatedSoFar = this.donations.get(donor, {defaultValue: BigInt(0)})
donatedSoFar+=donationAmount;
this.donations.set(donor,donatedSoFar);
near.log(`Thank you ${donor} for donating ${donationAmount}! You donated a total of ${donatedSoFar}`);
}
Here, we have passed payableFunction:true
to decorator because the caller will attach some near amount with function call. It's similar to payable
in solidity.
- near.predecessorAccountId(): To get the caller account ID.
-
near.attachedDeposit(): Near attached to the function call like
msg.value
in solidity. - this.donations.get(): to get the donor account ID and previously donated amount (if any, otherwise default value is 0) from the hashmap.
- this.donations.set: updating new donation amount for the caller in hashmap.
-
near.log: To log any message on blockchain explorer when transactions complete. Same as
console.log
in javascript.
Wow! Donation function is complete. Let's move to withdraw_funds
function which will allow the admin (the contract deployer) to withdraw the donations from the contract.
@call({privateFunction:true})
withdraw_funds(){
let balance=near.accountBalance() - BigInt('10000000000000000000000000');
const promise = near.promiseBatchCreate(this.beneficiary)
near.promiseBatchActionTransfer(promise, balance);
}
Notice the privateFunction:true
we have passed to the decorator because we want this function to only be called by the admin.
Private functions work differently in Near than EVM chains.
There's a concept of access keys in Near which is similar to Private keys but are shareable. By default, the user who deploys the contract at first has full access to smart contract functions including private functions. It enables the user to transfer tokens, cross-contract calls, and even update the contract after deployment (we will talk about this in the end).
To know more about new concepts, and cool stuff on Near visit my recent blog for an easy explanation: https://dev.to/rishabhraghwendra/near-blockchain-development-guide-for-ethereum-developers-1f1n
We are also leaving 10 Near in the contract for future use to pay for gas fees in the future:
near.accountBalance() - BigInt('10000000000000000000000000');
Since smart contract is built using Typescript it also has a Promises concept. Every token transfer and cross-contract call returns a promise. In this case, we have like this:
const promise = near.promiseBatchCreate(this.beneficiary)
Remember we can't use async/await in the contract. It's not supported.
To resolve this promise we can do something like this:
near.promiseBatchActionTransfer(promise, balance);
Now let's create the function to get all the donations list
@view({})
get_all_donations(){
return this.donations.toArray();
}
It's a simple view function call that converts the donations hashmap into an array and returns it to the caller.
To get the balance of the contract:
@view({})
view_balance(){
return near.accountBalance();
}
Deploy the smart contract by running npm run deploy
in the contract folder. It will also output the transaction ID link on the near test net where the contract has been deployed.
Copy the transaction hash and search it on https://testnet.nearblocks.io/ for better UI.
Note: Whenever you deploy a contract using the above template it will automatically store the contract address in the contract/neardev/dev-account.env
environment variable which will be automatically taken up by the frontend.
Now smart contract is done. Since it's not a frontend tutorial I will build a simple frontend and connect it with smart contract.
Building the simple frontend
The frontend is made using Vite React. If your frontend is not running, run it by running npm run dev
in the frontend folder.
I am using Material UI to get some UI components. Install it in frontend folder using:
npm install @mui/material @emotion/react @emotion/styled
Go to App.jsx
file and paste below code:
import * as nearAPI from "near-api-js";
import React, { useState } from "react";
import Card from "@mui/material/Card";
import CardContent from "@mui/material/CardContent";
import Button from "@mui/material/Button";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import "./App.css";
import { SignInButton,SignOutButton } from "./components/SignInButton.jsx";
const contractId=process.env.CONTACT_NAME;
export default function App({contractId, wallet }) {
const [donorsList, setDonorsList] = useState([]);
const [depositValue, setDepositValue] = useState();
const [totalFunds, setTotalFunds] = useState('');
// Get blockchian state once on component load
useEffect(() => {
getDontations()
.then((response) => {
const { utils } = nearAPI;
let donors = response.map((donor) => ({
donor: donor[0],
amount: utils.format.formatNearAmount(donor[1]),
}));
setDonorsList(donors);
console.log("response: ", response);
})
.catch(alert)
getTotalFunds().then((response)=>{
let funds =nearAPI.utils.format.formatNearAmount(response);
funds=funds.split(".");
let wholeNumber = funds[0];
let decimalPart='0';
if(funds.length>=2){
decimalPart=funds[1].padEnd(2,'0').slice(0,2);
}
setTotalFunds(wholeNumber+'.'+decimalPart);
})
}, []);
const getTotalFunds = async()=>{
return wallet.viewMethod({ method: "view_balance", contractId })
}
function donate(e) {
e.preventDefault();
const deposit = nearAPI.utils.format.parseNearAmount(depositValue);
// use the wallet to send the funds to the contract
wallet
.callMethod({
method: "donate",
deposit,
contractId,
})
.then(async () => {
console.log("Deposited!!");
return getDontations();
})
.then(setValueFromBlockchain)
}
function getDontations() {
// use the wallet to query the all contract's donations
return wallet.viewMethod({ method: "get_all_donations", contractId });
}
return (
<>
{wallet.accountId ?<SignOutButton accountId={wallet.accountId} onClick={()=>{wallet.signOut()}}/>:<SignInButton onClick={()=>{wallet.signIn()}}/>}
<main>
<div className="flex">
<h1>Welcome To Fund Me Contract on Near</h1>
<h2>(Total Funds Raised: {totalFunds} Near)</h2>
<div className="container-div">
<div className="txn-list">
<h3>All Donors</h3>
<TableContainer component={Paper}>
<Table
sx={{ minWidth: 400 }}
size="small"
aria-label="a dense table"
>
<TableHead>
<TableRow>
<TableCell>Donor</TableCell>
<TableCell align="right">Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{donorsList.map((row) => (
<TableRow
key={row.name}
sx={{
"&:last-child td, &:last-child th": { border: 0 },
}}
>
<TableCell component="th" scope="row">
{row.donor}
</TableCell>
<TableCell align="right">{row.amount} Near</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</div>
<div className="fund-me">
<h3>Fund Me</h3>
<Card>
<CardContent sx={{display:'flex',flexDirection:'column',gap:'1.3rem'}}>
<label>Enter Amount</label>
<input
autoComplete="off"
defaultValue={0}
className="amount"
type="number"
onChange={(e)=>setDepositValue(e.target.value)}
/>
<Button variant="contained" size="small" sx={{width:'100%'}} onClick={donate}>Submit</Button>
</CardContent>
</Card>
</div>
</div>
</div>
</main>
</>
);
}
The template has the helper class called Wallet
in frontend/src/utils/near-wallet.js
which has the all methods to call the call
function on the contract as well as the view
functions.
Checkout main.jsx
to know how the template setups the Class object at the app startup.
In App.css
paste this:
.flex{
display: flex;
gap: 1rem;
flex-direction: column;
align-items: center;
justify-content: center;
}
.container-div{
display: flex;
flex-direction: column-reverse;
gap: 1rem;
width: 100%;
align-items: center;
justify-content: center;
}
.txn-list{
width: 100%;
max-width: 600px;
}
.fund-me{
width: 100%;
max-width: 600px;
}
.amount{
border: 2px solid grey;
border-radius: 0.3rem;
}
And your first DApp on Near Blockchain is ready!! 🚀
I hope you liked the blog. If you have any doubts let me know in the comments.
To support me, you can donate me some Near on rishabhrag.near
🥳
Posted on October 29, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.