โจ An opinionated and fully typed hook-based project to improve DX and simplify interaction with proxy accounts and XCM on Polkadot.
๐ Tempora is an example of how transactions can be automated by leveraging cross-chain communication.
Steps required to run the project locally:
We recommend downloading the corresponding (
linux
ormac
) executable file from the Zombienet Github releases page. Move the downloaded file to thezombienet
folder, give it execute permissions, rename it to "zombienet" and that's it! โ
- Get the following chain runtimes with specific version:
-
- Install it globally with the following command:
cargo install --git https://github.com/paritytech/polkadot --tag v0.9.43 polkadot --locked
- Install it globally with the following command:
-
- Clone the repository in the folder
zombienet/OAK-blockchain
. - Checkout the specific version:
git checkout tags/v2.1.4
- Build it using this guide.
- Clone the repository in the folder
-
- Clone repository in the folder
zombienet/Astar
. - Checkout the specific version:
git checkout tags/v5.30.0
- Build it using this guide.
- Clone repository in the folder
-
Execute Zombienet using the turing-shibuya.toml configuration file on the
zombienet
path:zombienet spawn turing-shibuya.toml
. -
Deploy and instantiate the Tempora contract on the Shibuya Dev chain and save the contract address for the
.env.local
file. -
Deploy and instantiate an OpenBrush PSP22 token (Source code example) on the Shibuya Dev chain and save the contract address, token name and token decimals values for the
.env.local
file. -
Whitelist the PSP22 token in the Tempora contract using the
add_token_to_whitelist
message. It receives a token contract address as a parameter.
The message should be called with the Tempora Admin account (the account that initialized the smart contract).
- Initially, it's required to install dependencies:
yarn install
- Create a local environment file
.env.local
and add the following variables:
NEXT_PUBLIC_CHAIN_ENVIRONMENT=<CHAINS_ENVIRONMENT>
NEXT_PUBLIC_CONTRACT_ADDRESS=<TEMPORA_CONTRACT_ADDRESS>
NEXT_PUBLIC_PSP22_TOKEN_NAME=<PSP22_TOKEN_NAME>
NEXT_PUBLIC_PSP22_TOKEN_ADDRESS=<PSP22_TOKEN_CONTRACT_ADDRESS>
NEXT_PUBLIC_PSP22_TOKEN_DECIMALS=<PSP22_TOKEN_DECIMALS>
The
NEXT_PUBLIC_CHAIN_ENVIRONMENT
variable can have the following values:
dev
to use a local environment (Shibuya Dev and Turing Dev)testing
to use Rococo (Rocstar and Turing Staging)kusama
to use Kusama (Shiden and Turing Network).
- Then, you can run the following scripts:
Command | Description |
---|---|
yarn dev |
Run dev server |
yarn build |
Build project |
yarn start |
Run prod project |
yarn lint |
Run lint scanner |
yarn prettier |
Prettify code |
yarn test |
Run tests |
- Polkadot JS: Official Polkadot library and utilities
- useink: Abstractions library based on React hooks.
- ๐ Next.js: React framework.
- ๐งฉ Radix UI: Low-level UI component library.
- ๐จ Tailwind CSS: Utility-first CSS framework.
- ๐ Recoil: State management library.
- ๐ react-query: Powerful asynchronous state management for TS/JS.
- ๐ react-hook-form: Form handling library.
- โ Zod: TypeScript-first schema validation with static type inference.
- ๐งช Vitest: Lightweight test runner.
app
: Next.js pagessrc
:src/components
: Business componentssrc/core
: Generic UI components and common modelssrc/lib
: Business logic -> Blockchain hooks, helpers, models, states, configurations, contracts metadata, etc.
If you are looking for a user guide: click here
To use this project as a starting point for your own DApp:
- Install packages in your app
yarn add @oak-network/types @polkadot/keyring @polkadot/types @polkadot/util-crypto @polkadot/api @polkadot/util @polkadot/typegen recoil useink
- Setup Recoil, follow Recoil Getting Started for more information (you can use any other state management approach).
function App() {
return (
<RecoilRoot>
<Application />
</RecoilRoot>
);
}
-
Copy and Paste
src/lib
folder in your application. -
If you wish to use a different configuration for the origin/target chains you will need to replace constants in
lib/config/chainsConfig.ts
.TIP: You can find all the configurable constants by searching
// CONFIGURABLE
in the code. -
Include
TemporaProvider
as a provider in your application. This will setup all the necessary configurations along with useink Provider. -
Now you can start using the hooks provided in the folder
lib/hooks
Hook to encapsulate the UI from the specific library implementation. It combines the functionality of useink useWallet
and useBalance
.
Read account info:
const { account, getBalance, useBalance } = useWallet();
Manage connection:
const { connect, disconnect } = useWallet();
Configures globally the origin and target chains configuration, making it available in chainsConfigState atom state. Also gets the async properties needed
// This will set the chains config store internally
const originChain = useChainsConfig();
Hook for managing proxy accounts and related operations.
const { calculateProxies } = useProxyAccounts();
useEffect(() => {
if (!account || !apisReady) return;
calculateProxies();
}, [account, apisReady]);
const { proxiesExist } = useProxyAccounts();
const { data: proxiesExistData, isLoading: proxiesExistsIsLoading } = useQuery({
queryKey: ['proxiesExist', originProxyAddress, targetProxyAddress],
queryFn: proxiesExist,
enabled: apisReady && proxiesCalculated,
});
const { getProxiesBalances } = useProxyAccounts();
useEffect(() => {
if (proxiesExist) getProxiesBalances();
}, [proxiesExist]);
// Using react-query
const { createAccounts } = useProxyAccounts();
const { mutate: createAccountsMutate, isPending: creatingAccounts } =
useMutation({
mutationFn: () =>
createAccounts(
proxiesExistData!.originExists,
proxiesExistData!.targetExists
),
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['proxiesExist', originProxyAddress, targetProxyAddress],
});
},
});
const { calculateTotalTopUpBalances } = useProxyAccounts();
const { originTopUpBalance, targetTopUpBalance } = calculateTotalTopUpBalances(
originTotalFeeEstimation,
targetTotalFeeEstimation
);
const { getTopUpProxyAccountsExtrinsics, calculateTotalTopUpBalances } =
useProxyAccounts();
const { originTopUpBalance, targetTopUpBalance } = calculateTotalTopUpBalances(
originTotalFeeEstimation,
targetTotalFeeEstimation
);
const topUpProxyAccountExtrinsics = getTopUpProxyAccountsExtrinsics(
originTopUpBalance,
targetTopUpBalance
);
return await signAndSendPromise(
batchTransactions(api, [...topUpProxyAccountExtrinsics]),
account
);
Hook for estimating the fees of the origin and target extrinsics including XCM operations.
const { getOriginExtrinsicFeeEstimation } = useFeeEstimation();
const originFeeEstimation =
await getOriginExtrinsicFeeEstimation(originExtrinsic);
const { getTargetExtrinsicFeeEstimation } = useFeeEstimation();
const targetFeeEstimation =
await getTargetExtrinsicFeeEstimation(targetExtrinsic);
Hook to generate extrinsics, trigger fee estimation calculation and schedule a payment from the origin chain to the target chain through XCM.
// Using react-query
const { generateExtrinsicsAndEstimate } = useSchedulePayment();
const { data: newPaymentSummary, isLoading } = useQuery({
queryKey: ['createPaymentSummary'],
queryFn: () => generateExtrinsicsAndEstimate(newPaymentConfiguration),
});
// Using react query
const { createAndSaveScheduledPayment } = useSchedulePayment();
const { mutate, isPending, isSuccess } = useMutation({
mutationFn: (newPaymentSummary: NewPaymentSummary) => {
const {
targetFeeEstimation,
taskScheduleExtrinsic,
getActionsOnTaskScheduled,
} = newPaymentSummary;
return createAndSaveScheduledPayment(
targetFeeEstimation,
taskScheduleExtrinsic,
getActionsOnTaskScheduled
);
},
});
// Using react query
const {
mutate: deleteScheduledPaymentMutation,
isPending: isDeletingScheduledPayment,
} = useMutation({
mutationFn: async () => {
return await deleteScheduledPayment(temporaScheduleId, oakTaskId);
},
});
This file contains helper functions for interacting with the Oak.js API.
scheduleXcmpTaskThroughProxy()
: Schedules an XCMP task through a proxy using the automationTime module in the provided API.cancelTaskWithScheduleAs()
: Cancels a task with a schedule as a given account using the automationTime module in the provided API.
This file contains helper functions for interacting with the PolkadotJS API.
transfer()
: Creates a transaction to transfer a specified amount to a given address.crossChainTransfer()
: Creates a cross-chain transaction to transfer a specified asset to a given address.signAndSendPromise()
: Signs and sends a transaction using a given account's address and signer.batchTransactions()
: Batches multiple transactions together.xcmLocationToAssetIdNumber()
: Converts a XCM location to an asset ID number.getFormattedBalance()
: Formats the given balance into a human-readable string with the associated chain token.getFormattedTokenAmount()
: Formats the given amount into a human-readable string with the associated chain token.getTokenSymbol()
: Retrieves the chain token symbol from the provided API.getTokenBalanceOfAccount()
: Gets the balance of a specific token for a given account.getDefaultAssetBalance()
: Queries the balance of the default asset for a given address.getExtrinsicWeight()
: Retrieves the extrinsic weight for a given SubmittableExtrinsic using the paymentInfo function.getAssetMetadata()
: Retrieves the asset metadata for a specified asset ID using the assetRegistry query.extrinsicViaProxy()
: Creates an extrinsic to be executed via a proxy.queryWeightToFee()
: Queries the weight-to-fee conversion for a given weight using the transactionPaymentApi.sendXcm()
: Sends an XCM message to a specified destination using the polkadotXcm module.
This file contains helper functions for interacting with smart contracts using the PolkadotJS API.
getContractApi()
: Retrieves the contract API for a given contract address and metadata using the provided API.queryContract()
: Executes a given message dry-run for a given contract address using a contract API.getExecuteContractExtrinsic()
: Generates an extrinsic to execute a given message for a given contract address using a contract API.
This file contains helper functions for managing proxy accounts.
calculateProxyAccounts()
: Calculates proxy accounts for a given origin and target chain.validateProxyAccount()
: Validates a proxy account against a given address on the parachain.createProxyAccount()
: Creates a proxy account with the specified permissions for a given account using the provided API.getDerivativeAccount()
: Generates a derivative account address for a given account and destination parachain using XCM V3.getCrossChainTransferParameters()
: Generates cross-chain transfer parameters for a given amount, decoded account address, and target parachain ID.
This file contains the functions of the helper class for building a XCM message.
build()
: Builds the XCM message.addWithdrawAsset()
: Adds an Asset to the WithdrawAsset instruction of the XCM message.addBuyExecution()
: Adds a BuyExecution instruction to the XCM message.addTransact()
: Adds a Transact instruction to the XCM message.addRefundSurplus()
: Adds a RefundSurplus instruction to the XCM message.addDepositAsset()
: Adds a DepositAsset instruction to the XCM message.
Our unit tests cover the main hooks functionality.
Run on the root path: yarn test
Our tests cover the main messages functionality.
Run on the /contracts/tempora_contract
folder: cargo test
The easiest way to deploy this Next.js app is to use the Vercel Platform.
Check out the Next.js deployment documentation for more details.