Introduction
Smart contracts on the blockchain networks are like the the powerhouse that need needed to be controlled and interacted with by workers.
To do this, PAPI(Polkadot-API) is the messenger that allows a seamless interaction with Polkadot smart contracts, it provides modular functions for building decentralized Applications.
In this article, I will show you how to effectively interact with an ink! smart contract using PAPI. We will also use ReactiveDOT to interact with the generated contract typescript bindings in our frontend.
- To get started with the project create a new inkathon project using
npx create-inkathon-app@latest
and follow the prompts. - Change directory to the project you just created using
cd pizza_charity
and runbun run dev
, this will install all dependencies to get ahead in the project.
For the smart contract part, generate and build your pizza charity smart contract in the following steps.
cd contracts/src
cargo contract new pizza_charity
- Add your new contract to the
contracts/Cargo.toml
file.
[workspace]
members = [
"src/flipper",
"src/pizza_charity",
]
- After building your contract completely, build and generate typescript bindings using:
bun run build && bun run codegen
- Add the code snippet below to the
contracts/scripts/deploy.ts
before deployment to initialize the constructor function in your smart contract and supply the default variables.
const main = async () => {
const initResult = await initApi()
const deployResult = await deployContract(initResult, "pizza_limited", contracts.pizza_limited, "new", {
max_order_per_user: 5,
})
await writeAddresses({ pizza_limited: deployResult })
}
- Deploy your contract to any blockchain of your choice with:
CHAIN=<chain-name> bun deploy
Replace with the actual name of the chain you want to deploy to. After a successful deployment, you should get the deployment addresses in/contracts/deployments/pizza_charity/<chain>.ts
. That is all for the smart contract, let's go ahead with interacting with the smart contract on the frontend.
The reamaining part of this tutorial will be focused on the frontend interaction with the smart contract you just deployed.
- The first step is to configure the frontend of the application to the chain where you deployed your contract, go to
frontend/src/lib/reactive-dot/config.ts
and export your chain configuration as seen below. This configuration sets passethub as the chain on which are interacting with the smart contract, and it sets the frontend ready for wallets interaction with the deployed smart contract.
export const config = defineConfig({
chains: {
passethub: {
descriptor: passethub,
provider: getWsProvider("wss://testnet-passet-hub.polkadot.io"),
},
// Add more chains here
},
ssr: true,
wallets: [new InjectedWalletProvider()],
})
- Import the contract deployment into the
frontend/src/lib/inkathon/deployments.ts
file.
import { contracts } from "@polkadot-api/descriptors"
import * as pizzaPassethub from "contracts/deployments/pizza_limited/passethub"
export const pizza = {
contract: contracts.pizza_limited,
evmAddresses: {
passethub: pizzaPassethub.evmAddress,
// Add more deployments here
},
ss58Addresses: {
passethub: pizzaPassethub.ss58Address,
// Add more deployments here
},
}
export const deployments = {
pizza
// Add more contracts here
}
This will import the generated contract descriptors from polkadot-api and also import the chain we want to interact with.
The contract deployment will be linked with the passethub chain addresses here.
- The next step is the actual interaction with the contract. We want to do two types of interaction with the contract.
- Query the contract and get some information from the contract, for example, you can get the total daily supply of pizza and the remaining pizza for the day.
- write and sign a transaction to the contract, here, you can request for a certain amount of pizza and get it delivered to you.
To do that, go to the frontend/src/components/web3/contract-card.tsx
file in the project's folder, and add the code below to query the contract and get the daily pizza supply and display it in your frontend.
const queryContract = useCallback(async () => {
setQueryIsLoading(true)
try {
if (!api || !chain) return
const storageResult = await contract.getStorage().getRoot()
const result = await contract.query("get_daily_supply", {
origin: ALICE
});
const newDailySupplyState = result.success ? result.value.response : undefined
setDailySupply(newDailySupplyState)
console.log(dailySupply);
const result1 = await contract.query("get_remaining_supply", {
origin: ALICE
});
const newRemainingSupplyState = result1.success ? result1.value.response : undefined
setRemainingSupply(newRemainingSupplyState)
const newMaxOrderPerUserState = storageResult.success ? storageResult.value.max_order_per_user : undefined
setMaxOrderPerUser(newMaxOrderPerUserState )
} catch (error) {
console.error(error)
} finally {
setQueryIsLoading(false)
}
}, [api, chain])
useEffect(() => {
queryContract()
}, [queryContract])
What exactly is happening in this code block?
- We created connection with the Polkadot-SDK node using ReviveSdk and also a connected the pizza contract with the
typedApi()
. - We also created an instance of the pizza contract on the passethub chain.
- We are reading the root storage of the contract, this allows us to query every stored items in the contract.
- We now used
contract.query()
to read the contract's functions and return the responses such as getting the total daily supply of pizza.
Let us write a signed transaction to the contract to request for some pizza.
const orderPizza = useCallback(async () => {
if (!api || !chain || !signer) return
// Map account if not mapped
const isMapped = await sdk.addressIsMapped(signerAddress)
if (!isMapped) {
toast.error("Account not mapped. Please map your account first.")
return
}
// Send transaction
const tx = contract
.send("order_pizza",
{
data: {
quantity_ordered: quantityOrdered
},
origin: signerAddress
},
)
.signAndSubmit(signer)
.then((tx) => {
queryContract()
if (!tx.ok) throw new Error("Failed to send transaction here, please check it", { cause: tx.dispatchError })
})
toast.promise(tx, {
loading: "Sending transaction...",
success: "Successfully Ordered, your Pizza is on its way",
error: "Failed to send transaction here too",
})
}, [signer, api, chain])
Here, the first thing we did was to check if the accounts interacting with the contract is supported by the substrate runtime, if not, the function can prompts to map the account's address to the Polkadot type.
We also wrote a contract.send()
function that takes three arguments;
- The actual function you want to call which is
order_pizza
function. - The data argument that supplies the contract with the quantity of pizza you want to request, and,
- The origin of the transaction which is the user's signer address.
And lastly, we have a cron job that runs every 24 hours function that reset the daily pizza supply every 24 hours.
Conclusion.
In this project, we demonstrated the ease of deploying ink! smart contracts and interacted with it from the frontend using Polkadot-API.
With your knowledge of ink! smart contract develpment and frontend web development, polkadot-API offers you a level playground to make your apps available to users without any hassle.
Top comments (0)