[Engine] Add FAILED status to execution result and update error handling (#7065)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"thirdweb": patch
|
||||
---
|
||||
|
||||
Return timestamps in Engine.getTransactionStatus()
|
||||
+4
@@ -32,6 +32,10 @@ type ExecutionResult4337Serialized =
|
||||
| {
|
||||
status: "QUEUED";
|
||||
}
|
||||
| {
|
||||
status: "FAILED";
|
||||
error: string;
|
||||
}
|
||||
| {
|
||||
status: "SUBMITTED";
|
||||
monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR";
|
||||
|
||||
+2
-2
@@ -335,7 +335,6 @@ export async function createManagementAccessToken(props: {
|
||||
});
|
||||
if (res.success) {
|
||||
const data = res.data;
|
||||
// store the management access token in the project
|
||||
await updateProjectClient(
|
||||
{
|
||||
projectId: props.project.id,
|
||||
@@ -354,8 +353,9 @@ export async function createManagementAccessToken(props: {
|
||||
],
|
||||
},
|
||||
);
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
throw new Error(`Failed to create management access token: ${res.error}`);
|
||||
}
|
||||
|
||||
export function maskSecret(secret: string) {
|
||||
|
||||
+6
-1
@@ -36,12 +36,17 @@ export function TransactionDetailsUI({
|
||||
transactionHash,
|
||||
confirmedAt,
|
||||
createdAt,
|
||||
errorMessage,
|
||||
executionParams,
|
||||
executionResult,
|
||||
} = transaction;
|
||||
|
||||
const status = executionResult?.status as keyof typeof statusDetails;
|
||||
const errorMessage =
|
||||
executionResult && "error" in executionResult
|
||||
? executionResult.error
|
||||
: executionResult && "revertData" in executionResult
|
||||
? executionResult.revertData?.revertReason
|
||||
: null;
|
||||
|
||||
const chain = chainId ? idToChain.get(Number.parseInt(chainId)) : undefined;
|
||||
const explorer = chain?.explorers?.[0];
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
"@radix-ui/react-switch": "^1.2.2",
|
||||
"@radix-ui/react-tooltip": "1.2.3",
|
||||
"@tanstack/react-query": "5.74.4",
|
||||
"@thirdweb-dev/engine": "0.0.19",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"date-fns": "4.1.0",
|
||||
|
||||
@@ -9,16 +9,64 @@ export function useEngineTxStatus(queueId: string | undefined) {
|
||||
if (!queueId) throw new Error("No queue ID provided");
|
||||
const res = await get_engine_tx_status(queueId);
|
||||
|
||||
const txStatus: EngineTxStatus = {
|
||||
queueId: queueId,
|
||||
status: res.result.status,
|
||||
chainId: res.result.chainId,
|
||||
transactionHash: res.result.transactionHash,
|
||||
queuedAt: res.result.queuedAt,
|
||||
sentAt: res.result.sentAt,
|
||||
minedAt: res.result.minedAt,
|
||||
cancelledAt: res.result.cancelledAt,
|
||||
};
|
||||
let txStatus: EngineTxStatus;
|
||||
switch (res.status) {
|
||||
case "QUEUED": {
|
||||
txStatus = {
|
||||
queueId: queueId,
|
||||
status: "queued",
|
||||
chainId: res.chain.id.toString(),
|
||||
transactionHash: null,
|
||||
queuedAt: res.createdAt,
|
||||
sentAt: null,
|
||||
minedAt: null,
|
||||
cancelledAt: null,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "SUBMITTED": {
|
||||
txStatus = {
|
||||
queueId: queueId,
|
||||
status: "sent",
|
||||
chainId: res.chain.id.toString(),
|
||||
transactionHash: null,
|
||||
queuedAt: res.createdAt,
|
||||
sentAt: res.createdAt,
|
||||
minedAt: null,
|
||||
cancelledAt: null,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "CONFIRMED": {
|
||||
txStatus = {
|
||||
queueId: queueId,
|
||||
status: "mined",
|
||||
chainId: res.chain.id.toString(),
|
||||
transactionHash: res.transactionHash,
|
||||
queuedAt: res.createdAt,
|
||||
sentAt: res.confirmedAt,
|
||||
minedAt: res.confirmedAt,
|
||||
cancelledAt: null,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "FAILED": {
|
||||
txStatus = {
|
||||
queueId: queueId,
|
||||
status: "errored",
|
||||
chainId: res.chain.id.toString(),
|
||||
transactionHash: null,
|
||||
queuedAt: res.createdAt,
|
||||
sentAt: null,
|
||||
minedAt: null,
|
||||
cancelledAt: res.cancelledAt,
|
||||
};
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown engine tx status: ${res}`);
|
||||
}
|
||||
}
|
||||
|
||||
return txStatus;
|
||||
},
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
"use server";
|
||||
|
||||
import { Engine } from "@thirdweb-dev/engine";
|
||||
|
||||
import { Engine, defineChain, encode, getContract } from "thirdweb";
|
||||
import { multicall } from "thirdweb/extensions/common";
|
||||
import * as ERC20 from "thirdweb/extensions/erc20";
|
||||
import * as ERC1155 from "thirdweb/extensions/erc1155";
|
||||
import { THIRDWEB_CLIENT } from "../../lib/client";
|
||||
const BACKEND_WALLET_ADDRESS = process.env.ENGINE_BACKEND_WALLET as string;
|
||||
const ENGINE_VAULT_ACCESS_TOKEN = process.env
|
||||
.ENGINE_VAULT_ACCESS_TOKEN as string;
|
||||
|
||||
const engine = new Engine({
|
||||
url: process.env.ENGINE_URL as string,
|
||||
accessToken: process.env.ENGINE_ACCESS_TOKEN as string,
|
||||
const serverWallet = Engine.serverWallet({
|
||||
address: BACKEND_WALLET_ADDRESS,
|
||||
client: THIRDWEB_CLIENT,
|
||||
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
export async function airdrop_tokens_with_engine(params: {
|
||||
@@ -17,20 +23,37 @@ export async function airdrop_tokens_with_engine(params: {
|
||||
amount: string;
|
||||
}[];
|
||||
}) {
|
||||
const res = await engine.erc20.mintBatchTo(
|
||||
params.chainId.toString(),
|
||||
params.contractAddress,
|
||||
BACKEND_WALLET_ADDRESS,
|
||||
{
|
||||
data: params.receivers,
|
||||
},
|
||||
const contract = getContract({
|
||||
address: params.contractAddress,
|
||||
chain: defineChain(params.chainId),
|
||||
client: THIRDWEB_CLIENT,
|
||||
});
|
||||
const data = await Promise.all(
|
||||
params.receivers.map((receiver) =>
|
||||
encode(
|
||||
ERC20.mintTo({
|
||||
contract,
|
||||
to: receiver.toAddress,
|
||||
amount: receiver.amount,
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
const tx = multicall({
|
||||
contract,
|
||||
data,
|
||||
});
|
||||
|
||||
return res.result;
|
||||
const res = await serverWallet.enqueueTransaction({ transaction: tx });
|
||||
|
||||
return res.transactionId;
|
||||
}
|
||||
|
||||
export async function get_engine_tx_status(queueId: string) {
|
||||
const status = await engine.transaction.status(queueId);
|
||||
const status = await Engine.getTransactionStatus({
|
||||
client: THIRDWEB_CLIENT,
|
||||
transactionId: queueId,
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -49,17 +72,19 @@ type MintNFTParams = {
|
||||
};
|
||||
|
||||
export async function mint_erc1155_nft_with_engine(params: MintNFTParams) {
|
||||
const res = await engine.erc1155.mintTo(
|
||||
params.chainId.toString(),
|
||||
params.contractAddress,
|
||||
BACKEND_WALLET_ADDRESS,
|
||||
{
|
||||
receiver: params.toAddress,
|
||||
metadataWithSupply: params.metadataWithSupply,
|
||||
},
|
||||
);
|
||||
const tx = ERC1155.mintTo({
|
||||
contract: getContract({
|
||||
address: params.contractAddress,
|
||||
chain: defineChain(params.chainId),
|
||||
client: THIRDWEB_CLIENT,
|
||||
}),
|
||||
nft: params.metadataWithSupply.metadata,
|
||||
to: params.toAddress,
|
||||
supply: BigInt(params.metadataWithSupply.supply),
|
||||
});
|
||||
const res = await serverWallet.enqueueTransaction({ transaction: tx });
|
||||
|
||||
return res.result;
|
||||
return res.transactionId;
|
||||
}
|
||||
|
||||
type ClaimNFTParams = {
|
||||
@@ -71,16 +96,17 @@ type ClaimNFTParams = {
|
||||
};
|
||||
|
||||
export async function claim_erc1155_nft_with_engine(params: ClaimNFTParams) {
|
||||
const res = await engine.erc1155.claimTo(
|
||||
params.chainId.toString(),
|
||||
params.contractAddress,
|
||||
BACKEND_WALLET_ADDRESS,
|
||||
{
|
||||
receiver: params.receiverAddress,
|
||||
quantity: params.quantity.toString(),
|
||||
tokenId: params.tokenId,
|
||||
},
|
||||
);
|
||||
const tx = ERC1155.claimTo({
|
||||
contract: getContract({
|
||||
address: params.contractAddress,
|
||||
chain: defineChain(params.chainId),
|
||||
client: THIRDWEB_CLIENT,
|
||||
}),
|
||||
to: params.receiverAddress,
|
||||
tokenId: BigInt(params.tokenId),
|
||||
quantity: BigInt(params.quantity),
|
||||
});
|
||||
const res = await serverWallet.enqueueTransaction({ transaction: tx });
|
||||
|
||||
return res.result;
|
||||
return res.transactionId;
|
||||
}
|
||||
|
||||
@@ -34,49 +34,49 @@ export function AirdropCode() {
|
||||
}
|
||||
|
||||
const engineAirdropSendCode = `\
|
||||
const chainId = ${airdropExample.chainId};
|
||||
const contractAddress = "${airdropExample.contractAddress}";
|
||||
const addresses = ${JSON.stringify(
|
||||
airdropExample.receivers.map((x) => ({
|
||||
address: x.toAddress,
|
||||
quantity: x.amount,
|
||||
recipient: x.toAddress,
|
||||
amount: x.amount,
|
||||
})),
|
||||
null,
|
||||
2,
|
||||
)};
|
||||
|
||||
const url = \`\${YOUR_ENGINE_URL}\/contract/\${chainId}/\${contractAddress}\/erc1155\/airdrop\`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": "Bearer YOUR_SECRET_TOKEN",
|
||||
"Content-Type": "application/json",
|
||||
"X-Backend-Wallet-Address": "YOUR_BACKEND_WALLET_ADDRESS",
|
||||
},
|
||||
body: JSON.stringify({ addresses }),
|
||||
const contract = getContract({
|
||||
address: ${airdropExample.contractAddress},
|
||||
chain: defineChain(${airdropExample.chainId}),
|
||||
client: THIRDWEB_CLIENT,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
const queueId = data.queueId;
|
||||
const transaction = airdropERC20({
|
||||
contract,
|
||||
tokenAddress: ${airdropExample.contractAddress},
|
||||
contents: addresses,
|
||||
});
|
||||
|
||||
const serverWallet = Engine.serverWallet({
|
||||
address: BACKEND_WALLET_ADDRESS,
|
||||
client: THIRDWEB_CLIENT,
|
||||
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
const { transactionId } = await serverWallet.enqueueTransaction({ transaction });
|
||||
`;
|
||||
|
||||
const engineAirdropGetStatus = `\
|
||||
function getEngineTxStatus(queueId: string) {
|
||||
const url = \`\${YOUR_ENGINE_URL}\/transaction/\${queueId}\`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer YOUR_SECRET_TOKEN",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return data.result;
|
||||
}
|
||||
|
||||
// you can keep polling for the status until you get a status of either "mined" or "errored" or "cancelled"
|
||||
const result = await getEngineTxStatus(queueId);
|
||||
const result = await Engine.getTransactionStatus({
|
||||
client: THIRDWEB_CLIENT,
|
||||
transactionId: transactionId,
|
||||
});
|
||||
|
||||
console.log(result.status);
|
||||
|
||||
// or wait for the transaction to be mined (polls status until it's mined)
|
||||
const result = await Engine.waitForTransactionHash({
|
||||
client: THIRDWEB_CLIENT,
|
||||
transactionId: transactionId,
|
||||
});
|
||||
|
||||
console.log(result.transactionHash);
|
||||
`;
|
||||
|
||||
@@ -32,10 +32,10 @@ export function EngineAirdropPreview() {
|
||||
const res = await airdropMutation.mutateAsync();
|
||||
updateEngineTxStatus({
|
||||
chainId: airdropExample.chainId,
|
||||
queueId: res.queueId,
|
||||
queueId: res,
|
||||
});
|
||||
|
||||
setQueueId(res.queueId);
|
||||
setQueueId(res);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const airdropExample = {
|
||||
contractAddress: "0xcB30dB8FB977e8b27ae34c86aF16C4F5E428c0bE",
|
||||
contractAddress: "0x6E238275023A2575136CF60f655B6B2C0C58b4ac",
|
||||
chainId: 84532,
|
||||
chainName: "Base Sepolia",
|
||||
chainExplorer: "https://base-sepolia.blockscout.com",
|
||||
|
||||
@@ -13,7 +13,7 @@ export function MintCode() {
|
||||
|
||||
<div className="h-4" />
|
||||
<h3 className="mb-2 font-semibold text-xl tracking-tight">
|
||||
Send Transaction Request to Mint NFTs
|
||||
Send Transaction Request to Mint Dynamic NFTs
|
||||
</h3>
|
||||
<Code code={engineMintCode} lang="typescript" />
|
||||
|
||||
@@ -36,48 +36,45 @@ export function MintCode() {
|
||||
const engineMintCode = `\
|
||||
const chainId = ${mintExample.chainId};
|
||||
const contractAddress = "${mintExample.contractAddress}";
|
||||
const url = \`\${YOUR_ENGINE_URL}\/contract/\${chainId}/\${contractAddress}\/erc1155\/mint-to\`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": "Bearer YOUR_SECRET_TOKEN",
|
||||
"Content-Type": "application/json",
|
||||
"X-Backend-Wallet-Address": "YOUR_BACKEND_WALLET_ADDRESS",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
receiver: "0x....",
|
||||
metadataWithSupply: {
|
||||
metadata: {
|
||||
name: "...",
|
||||
description: "...",
|
||||
image: "...", // ipfs or https link to your asset
|
||||
},
|
||||
supply: "1",
|
||||
},
|
||||
})
|
||||
const contract = getContract({
|
||||
address: ${mintExample.contractAddress},
|
||||
chain: defineChain(${mintExample.chainId}),
|
||||
client: THIRDWEB_CLIENT,
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data.queueId);
|
||||
const transaction = mintTo({
|
||||
contract,
|
||||
to: "0x....",
|
||||
nft: {
|
||||
name: "...",
|
||||
description: "...",
|
||||
image: "...", // ipfs or https link to your asset
|
||||
},
|
||||
supply: "1",
|
||||
});
|
||||
|
||||
const serverWallet = Engine.serverWallet({
|
||||
address: BACKEND_WALLET_ADDRESS,
|
||||
client: THIRDWEB_CLIENT,
|
||||
vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN,
|
||||
});
|
||||
|
||||
const { transactionId } = await serverWallet.enqueueTransaction({ transaction });
|
||||
`;
|
||||
|
||||
const getEngineStatusCode = `\
|
||||
function getEngineTxStatus(queueId: string) {
|
||||
const url = \`\${YOUR_ENGINE_URL}\/transaction/\${queueId}\`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer YOUR_SECRET_TOKEN",
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return data.result;
|
||||
}
|
||||
|
||||
// you can keep polling for the status until you get a status of either "mined" or "errored" or "cancelled"
|
||||
const result = await getEngineTxStatus(queueId);
|
||||
const result = await Engine.getTransactionStatus({
|
||||
client: THIRDWEB_CLIENT,
|
||||
transactionId: transactionId,
|
||||
});
|
||||
|
||||
console.log(result.status);
|
||||
|
||||
// or wait for the transaction to be mined (polls status until it's mined)
|
||||
const result = await Engine.waitForTransactionHash({
|
||||
client: THIRDWEB_CLIENT,
|
||||
transactionId: transactionId,
|
||||
});
|
||||
|
||||
console.log(result.transactionHash);
|
||||
`;
|
||||
|
||||
@@ -231,10 +231,10 @@ export function EngineMintPreview() {
|
||||
});
|
||||
|
||||
// optimistic update
|
||||
queryClient.setQueryData(["engineTxStatus", result.queueId], {
|
||||
queryClient.setQueryData(["engineTxStatus", result], {
|
||||
status: "queued",
|
||||
chainId: mintExample.chainId.toString(),
|
||||
queueId: result.queueId,
|
||||
queueId: result,
|
||||
transactionHash: null,
|
||||
queuedAt: new Date().toISOString(),
|
||||
sentAt: null,
|
||||
@@ -242,7 +242,7 @@ export function EngineMintPreview() {
|
||||
cancelledAt: null,
|
||||
} satisfies EngineTxStatus);
|
||||
|
||||
setQueueId(result.queueId);
|
||||
setQueueId(result);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const mintExample = {
|
||||
contractAddress: "0x8CD193648f5D4E8CD9fD0f8d3865052790A680f6",
|
||||
contractAddress: "0x8a4E14591088bBce4a4Dcfa677C1af78d336d6FB",
|
||||
chainId: 84532,
|
||||
chainName: "Base Sepolia",
|
||||
chainExplorer: "https://base-sepolia.blockscout.com",
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function Page() {
|
||||
return (
|
||||
<ThirdwebProvider>
|
||||
<PageLayout
|
||||
title="Mint NFTs"
|
||||
title="Mint Dynamic NFTs"
|
||||
description={
|
||||
<>
|
||||
Allow your users to mint new tokens into any given contract. You
|
||||
|
||||
@@ -71,7 +71,7 @@ export function EngineWebhooksPreview() {
|
||||
|
||||
async function onSubmit(values: z.infer<typeof formSchema>) {
|
||||
const res = await claimMutation.mutateAsync(values.receiverAddress);
|
||||
setQueueId(res.queueId);
|
||||
setQueueId(res);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -49,6 +49,9 @@ export type ExecutionResult = Prettify<
|
||||
chain: Chain;
|
||||
from: string | undefined;
|
||||
id: string;
|
||||
createdAt: string;
|
||||
confirmedAt: string | null;
|
||||
cancelledAt: string | null;
|
||||
}
|
||||
>;
|
||||
|
||||
@@ -105,6 +108,9 @@ export async function getTransactionStatus(args: {
|
||||
const executionResult = data.executionResult as ExecutionResult4337Serialized;
|
||||
return {
|
||||
...executionResult,
|
||||
createdAt: data.createdAt,
|
||||
confirmedAt: data.confirmedAt,
|
||||
cancelledAt: data.cancelledAt,
|
||||
chain: getCachedChain(Number(data.chainId)),
|
||||
from: data.from ?? undefined,
|
||||
id: data.id,
|
||||
|
||||
Generated
+11
-35
Reference in New Issue
Block a user