diff --git a/.changeset/grumpy-areas-help.md b/.changeset/grumpy-areas-help.md new file mode 100644 index 00000000000..d5d14273e1a --- /dev/null +++ b/.changeset/grumpy-areas-help.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Return timestamps in Engine.getTransactionStatus() diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts index 66552766fc2..482409932ff 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/analytics/tx-table/types.ts @@ -32,6 +32,10 @@ type ExecutionResult4337Serialized = | { status: "QUEUED"; } + | { + status: "FAILED"; + error: string; + } | { status: "SUBMITTED"; monitoringStatus: "WILL_MONITOR" | "CANNOT_MONITOR"; diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts index b3c95bace1d..d84154a21f3 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/lib/vault.client.ts @@ -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) { diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx index 35dcd10329e..516981a60e2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/engine/cloud/tx/[id]/transaction-details-ui.tsx @@ -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]; diff --git a/apps/playground-web/package.json b/apps/playground-web/package.json index 237beecd136..63d916f4baa 100644 --- a/apps/playground-web/package.json +++ b/apps/playground-web/package.json @@ -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", diff --git a/apps/playground-web/src/app/engine/_hooks/useEngineTxStatus.ts b/apps/playground-web/src/app/engine/_hooks/useEngineTxStatus.ts index a33a1ce7509..cfbedc2a4ce 100644 --- a/apps/playground-web/src/app/engine/_hooks/useEngineTxStatus.ts +++ b/apps/playground-web/src/app/engine/_hooks/useEngineTxStatus.ts @@ -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; }, diff --git a/apps/playground-web/src/app/engine/actions.ts b/apps/playground-web/src/app/engine/actions.ts index d37b05f825f..aacca6fef93 100644 --- a/apps/playground-web/src/app/engine/actions.ts +++ b/apps/playground-web/src/app/engine/actions.ts @@ -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, + }); + + const res = await serverWallet.enqueueTransaction({ transaction: tx }); - return res.result; + 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; } diff --git a/apps/playground-web/src/app/engine/airdrop/_components/airdrop-code.tsx b/apps/playground-web/src/app/engine/airdrop/_components/airdrop-code.tsx index 5419ff200d6..c7c919da575 100644 --- a/apps/playground-web/src/app/engine/airdrop/_components/airdrop-code.tsx +++ b/apps/playground-web/src/app/engine/airdrop/_components/airdrop-code.tsx @@ -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 contract = getContract({ + address: ${airdropExample.contractAddress}, + chain: defineChain(${airdropExample.chainId}), + client: THIRDWEB_CLIENT, +}); + +const transaction = airdropERC20({ + contract, + tokenAddress: ${airdropExample.contractAddress}, + contents: addresses, + }); -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 serverWallet = Engine.serverWallet({ + address: BACKEND_WALLET_ADDRESS, + client: THIRDWEB_CLIENT, + vaultAccessToken: ENGINE_VAULT_ACCESS_TOKEN, }); -const data = await response.json(); -const queueId = data.queueId; +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 result = await Engine.getTransactionStatus({ + client: THIRDWEB_CLIENT, + transactionId: transactionId, +}); - const data = await response.json(); - return data.result; -} +console.log(result.status); -// you can keep polling for the status until you get a status of either "mined" or "errored" or "cancelled" -const result = await getEngineTxStatus(queueId); +// 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.status); +console.log(result.transactionHash); `; diff --git a/apps/playground-web/src/app/engine/airdrop/_components/airdrop-preview.tsx b/apps/playground-web/src/app/engine/airdrop/_components/airdrop-preview.tsx index 68cd4e932e3..3849529ff34 100644 --- a/apps/playground-web/src/app/engine/airdrop/_components/airdrop-preview.tsx +++ b/apps/playground-web/src/app/engine/airdrop/_components/airdrop-preview.tsx @@ -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 ( diff --git a/apps/playground-web/src/app/engine/airdrop/constants.ts b/apps/playground-web/src/app/engine/airdrop/constants.ts index 43234d34dbb..1ce71bf4c38 100644 --- a/apps/playground-web/src/app/engine/airdrop/constants.ts +++ b/apps/playground-web/src/app/engine/airdrop/constants.ts @@ -1,5 +1,5 @@ export const airdropExample = { - contractAddress: "0xcB30dB8FB977e8b27ae34c86aF16C4F5E428c0bE", + contractAddress: "0x6E238275023A2575136CF60f655B6B2C0C58b4ac", chainId: 84532, chainName: "Base Sepolia", chainExplorer: "https://base-sepolia.blockscout.com", diff --git a/apps/playground-web/src/app/engine/minting/_components/mint-code.tsx b/apps/playground-web/src/app/engine/minting/_components/mint-code.tsx index 884e3e82805..fc378086fcd 100644 --- a/apps/playground-web/src/app/engine/minting/_components/mint-code.tsx +++ b/apps/playground-web/src/app/engine/minting/_components/mint-code.tsx @@ -13,7 +13,7 @@ export function MintCode() {
@@ -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 contract = getContract({
+ address: ${mintExample.contractAddress},
+ chain: defineChain(${mintExample.chainId}),
+ client: THIRDWEB_CLIENT,
+});
-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 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 data = await response.json();
-console.log(data.queueId);
+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 result = await Engine.getTransactionStatus({
+ client: THIRDWEB_CLIENT,
+ transactionId: transactionId,
+});
- const data = await response.json();
- return data.result;
-}
+console.log(result.status);
-// you can keep polling for the status until you get a status of either "mined" or "errored" or "cancelled"
-const result = await getEngineTxStatus(queueId);
+// 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.status);
+console.log(result.transactionHash);
`;
diff --git a/apps/playground-web/src/app/engine/minting/_components/mint-preview.tsx b/apps/playground-web/src/app/engine/minting/_components/mint-preview.tsx
index efcfaa8cc89..a216f2b8ed6 100644
--- a/apps/playground-web/src/app/engine/minting/_components/mint-preview.tsx
+++ b/apps/playground-web/src/app/engine/minting/_components/mint-preview.tsx
@@ -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 (
diff --git a/apps/playground-web/src/app/engine/minting/constants.ts b/apps/playground-web/src/app/engine/minting/constants.ts
index 93b6b25e936..16db77bedfb 100644
--- a/apps/playground-web/src/app/engine/minting/constants.ts
+++ b/apps/playground-web/src/app/engine/minting/constants.ts
@@ -1,5 +1,5 @@
export const mintExample = {
- contractAddress: "0x8CD193648f5D4E8CD9fD0f8d3865052790A680f6",
+ contractAddress: "0x8a4E14591088bBce4a4Dcfa677C1af78d336d6FB",
chainId: 84532,
chainName: "Base Sepolia",
chainExplorer: "https://base-sepolia.blockscout.com",
diff --git a/apps/playground-web/src/app/engine/minting/page.tsx b/apps/playground-web/src/app/engine/minting/page.tsx
index e62fa77325b..f44c05d9b9c 100644
--- a/apps/playground-web/src/app/engine/minting/page.tsx
+++ b/apps/playground-web/src/app/engine/minting/page.tsx
@@ -7,7 +7,7 @@ export default function Page() {
return (