Withdraw ERC721 Assets
Learn how to withdraw ERC721 assets from Myria L2 to the Ethereum L1 network.
Prerequisites
- Mint or buy at least one ERC721 asset.
Withdrawal process
Withdrawal process of ERC721 assets includes four steps:
- Get a list of assets to withdraw
- Initiate the withdrawal
- Get the withdrawals list
- Complete the withdrawal
Implementation
- Myria SDK (React)
- Myria Marketplace
You can find the full React implementation for ERC721 withdrawals in the Myria React Samples repository.
1. Set up a new React project
Withdrawal of ERC721 tokens requires client-side interaction with the MetaMask wallet. It's recommended to use React to implement such behavior.
You can create a React app via Create React App. Note, the below project relies on Web3.js library and needs custom configuration.
2. Create a myria-client.ts
helper
It's recommended to have a separate .ts
file for quick access to the MyriaClient
.
For more details, use the Myria Core SDK overview.
3. Retrieve the assets
First, get a list of available assets in your wallet.
You can use the getAssetByStarkKey()
function of the OnchainAssetManager
.
- get-myria-erc721.ts
- MyriaAssets.tsx
The getMyriaErc721ByStarkKey()
function has the following parameters:
client
:MyriaClient
instancestarkKey
: Stark Key of your wallet
import { MyriaClient, OnchainAssetManager } from "myria-core-sdk";
export async function getMyriaErc721ByStarkKey(client: MyriaClient, starkKey: string) {
const assetManager: OnchainAssetManager = new OnchainAssetManager(client);
return await assetManager.getAssetByStarkKey(starkKey).then((data: any) => {
assets = data.data.items
});
}
import { MyriaClient } from "myria-core-sdk";
import { useEffect, useState } from "react";
import { getMyriaErc721ByStarkKey } from "./get-myria-erc721";
type Props = {
isConnected: boolean,
account: string,
starkKey: string,
client: MyriaClient
}
const Assets = ({ isConnected, account, starkKey, client }: Props) => {
const [assets, setAssets] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [err, setErr] = useState("");
useEffect(() => {
if(isConnected) {
const getErc721 = async () => {
setIsLoading(true);
try {
const result = await getMyriaErc721ByStarkKey(client, starkKey);
setAssets(result);
} catch (err: any) {
setErr(err.message);
} finally {
setIsLoading(false);
setIsLoaded(true);
}
}
getErc721();
}
}, []);
return (
<div>
{err && <code className="mt-3">{err}</code>}
{!isConnected && <p>Please connect your wallet first!</p>}
{isLoading && <p>Loading assets...</p>}
<div className="row align-items-start text-center mt-3">
{(assets && assets.length && isLoaded)
? assets.map((asset: any) => (
<div className="col mb-3" key={asset.id}>
<div className="card mry-card" key={asset.id} style={{ width: "14rem" }}>
<img src={asset.imageUrl ? asset.imageUrl : "/null.png"} alt={asset.name} className="card-img-top img-thumbnail" />
<div className="card-body">
<h5 className="card-title">{asset.name}</h5>
<p className="card-text">{asset.description}</p>
</div>
<div className="card-footer">
<small className="text-muted">#{asset.id} | {asset.publicId}</small>
</div>
</div>
</div>
))
: (isLoaded && !assets.length && <p>No assets available</p>)
}
</div>
</div>
);
}
export default Assets;
4. Initiate a withdrawal
You can initiate a withdrawal using the withdrawNftOffChain()
function of the WithdrawalModule
.
- withdraw-erc721.ts
- MyriaAssets.tsx
The withdrawErc721()
function has the following parameters:
client
:MyriaClient
instancenft
: an asset to withdrawaccount
: a public key of your walletstarkKey
: a Stark Key of your wallet
import { MyriaClient, WithdrawalModule, WithdrawNftOffChainParams } from "myria-core-sdk";
export async function withdrawErc721(client: MyriaClient, asset: any, account: string, starkKey: string) {
const withdrawalModule: WithdrawalModule = new WithdrawalModule(client);
const params: WithdrawNftOffChainParams = {
id: asset.id,
tokenId: asset.tokenId,
tokenAddress: asset.tokenAddress,
senderVaultId: asset.vaultId,
senderPublicKey: starkKey,
receiverPublicKey: account,
assetId: asset.assetId,
quantizedAmount: asset.amount
}
return await withdrawalModule.withdrawNftOffChain(params);
}
import { MyriaClient } from "myria-core-sdk";
import { useEffect, useState } from "react";
import { getMyriaErc721ByStarkKey } from "./get-myria-erc721";
import { withdrawErc721 } from "./withdraw-erc721";
type Props = {
isConnected: boolean,
account: string,
starkKey: string,
client: MyriaClient
}
const Assets = ({ isConnected, account, starkKey, client }: Props) => {
const [assets, setAssets] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [err, setErr] = useState("");
useEffect(() => {
if(isConnected) {
const getErc721 = async () => {
setIsLoading(true);
try {
const result = await getMyriaErc721ByStarkKey(client, starkKey);
setAssets(result);
} catch (err: any) {
setErr(err.message);
} finally {
setIsLoading(false);
setIsLoaded(true);
}
}
getErc721();
}
}, []);
const onWithdraw = async (asset: any) => {
return await withdrawErc721(client, asset, account, starkKey);
}
return (
<div>
{err && <code className="mt-3">{err}</code>}
{!isConnected && <p>Please connect your wallet first!</p>}
{isLoading && <p>Loading assets...</p>}
<div className="row align-items-start text-center mt-3">
{(assets && assets.length && isLoaded)
? assets.map((asset: any) => (
<div className="col mb-3" key={asset.id}>
<div className="card mry-card" key={asset.id} style={{ width: "14rem" }}>
<img src={asset.imageUrl ? asset.imageUrl : "/null.png"} alt={asset.name} className="card-img-top img-thumbnail" />
<div className="card-body">
<h5 className="card-title">{asset.name}</h5>
<p className="card-text">{asset.description}</p>
<p className="card-link" onClick={() => onWithdraw(asset)}>Withdraw NFT</p>
</div>
<div className="card-footer">
<small className="text-muted">#{asset.id} | {asset.publicId}</small>
</div>
</div>
</div>
))
: (isLoaded && !assets.length && <p>No assets available</p>)
}
</div>
</div>
);
}
export default Assets;
5. Get a list of withdrawals
You can retrieve a list of withdrawals using the getTransactionList()
function of the TransactionManager
.
- get-withdrawals-list.ts
- Erc721Withdrawals.tsx
The getWithdrawalsList()
function has the following parameters:
client
:MyriaClient
instancestarkKey
: a Stark Key of your wallet
import { MyriaClient, TransactionManager } from "myria-core-sdk";
export async function getWithdrawalsList(client: MyriaClient, starkKey: string) {
const trxManager: TransactionManager = new TransactionManager(client);
return await trxManager.getTransactionList(starkKey)
.then((data) => {
if (data.data) {
withdrawals = data.data
.filter((item: any) => item.transactionType === "WithdrawalRequest" && item.transactionStatus === "Success")
}
});
}
import { MyriaClient } from "myria-core-sdk";
import { useEffect } from "react";
import { useState } from "react";
import { getWithdrawalsList } from "./get-withdrawals-list";
type Props = {
isConnected: boolean,
account: string,
starkKey: string
client: MyriaClient
}
const Withdrawals = ({ isConnected, starkKey, client }: Props) => {
const [withdrawals, setWithdrawals] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [err, setErr] = useState("");
useEffect(() => {
if (isConnected) {
const getWithdrawals = async () => {
setIsLoading(true);
try {
const result = await getWithdrawalsList(client, starkKey);
setWithdrawals(result);
} catch (err: any) {
setErr(err.message);
} finally {
setIsLoading(false);
setIsLoaded(true);
}
}
getWithdrawals();
}
}, []);
return (
<div>
{err && <code className="mt-3">{err}</code>}
{!isConnected && <p>Please connect your wallet first!</p>}
{isLoading && <p>Loading...</p>}
<div className="row align-items-start text-center mt-3">
{(Array.isArray(withdrawals) && isLoaded)
? withdrawals.map((item: any) => (
<div className="col mb-3" key={item.assetId}>
<div className="card mry-card" style={{ width: "14rem" }}>
<div className="card-body">
<h5 className="card-title">#{item.tokenId}</h5>
<p className="card-text">Status: {item.transactionStatus}</p>
</div>
<div className="card-footer">
<small className="text-muted">{item.tokenType}</small>
</div>
</div>
</div>
))
: (isLoaded && !(Array.isArray(withdrawals)) && <p>No withdrawals available</p>)
}
</div>
</div>
);
}
export default Withdrawals;
6. Complete a withdrawal
You can complete a withdrawal using the withdrawAndMint()
function of the TransactionManager
.
- complete-erc721-withdrawal.ts
- Erc721Withdrawals.tsx
The completeErc721Withdrawal()
function has the following parameters:
client
:MyriaClient
instanceaccount
: a public key of your walletasset
: an asset to withdrawstarkKey
: a Stark Key of your wallet
import { MyriaClient, TokenType, WithdrawalModule, WithdrawAndMintParams } from "myria-core-sdk";
export async function completeErc721Withdrawal(client: MyriaClient, account: string, asset: any, starkKey: string) {
const withdrawalModule: WithdrawalModule = new WithdrawalModule(client);
const params: WithdrawAndMintParams = {
starkKey: starkKey,
walletAddress: account,
tokenType: TokenType.MINTABLE_ERC721,
assetType: asset.assetType,
tokenAddress: asset.tokenAddress,
mintingBlob: asset.blueprint
}
return await withdrawalModule.withdrawAndMint(params);
}
import { MyriaClient } from "myria-core-sdk";
import { useEffect } from "react";
import { useState } from "react";
import { completeErc721Withdrawal } from "./complete-erc721-withdrawal";
import { getWithdrawalsList } from "./get-withdrawals-list";
type Props = {
isConnected: boolean,
account: string,
starkKey: string
client: MyriaClient
}
const Withdrawals = ({ isConnected, account, starkKey, client }: Props) => {
const [withdrawals, setWithdrawals] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isLoaded, setIsLoaded] = useState(false);
const [err, setErr] = useState("");
const onCompleteWithdrawal = async (asset: any) => {
return await completeErc721Withdrawal(client, account, asset, starkKey);
}
useEffect(() => {
if (isConnected) {
const getWithdrawals = async () => {
setIsLoading(true);
try {
const result = await getWithdrawalsList(client, starkKey);
setWithdrawals(result);
} catch (err: any) {
setErr(err.message);
} finally {
setIsLoading(false);
setIsLoaded(true);
}
}
getWithdrawals();
}
}, []);
return (
<div>
{err && <code className="mt-3">{err}</code>}
{!isConnected && <p>Please connect your wallet first!</p>}
{isLoading && <p>Loading...</p>}
<div className="row align-items-start text-center mt-3">
{(withdrawals && withdrawals.length && isLoaded)
? withdrawals.map((item: any) => (
<div className="col mb-3" key={item.assetId}>
<div className="card mry-card" style={{ width: "14rem" }}>
<div className="card-body">
<h5 className="card-title">#{item.tokenId}</h5>
<p className="card-text">Status: {item.transactionStatus}</p>
{
(item.transactionStatus === "Pending") ? "" :
<p className="card-link" onClick={() => onCompleteWithdrawal(item)}>Complete Withdrawal</p>
}
</div>
<div className="card-footer">
<small className="text-muted">{item.tokenType}</small>
</div>
</div>
</div>
))
: (isLoaded && !withdrawals.length && <p>No withdrawals available</p>)
}
</div>
</div>
);
}
export default Withdrawals;