Commands

Send Transaction

Send transaction is our command that lets you write to arbitrary smart contracts. One important policy restriction we enforce is that we do not allow approvals. In order to use funds you must use the Signature Transfer function of permit2. This command is available from version 2.7.63 and above. As a design principle we expect you to only request funds for what will be consumed in the transaction.

For the example we will be using a sample contract on Optimism.

Crafting the payload

Send transaction will automatically create the permit2 signatures for you.

export type SendTransactionInput = {
  transaction: Transaction[];
  permit2?: Permit2[]; // Optional
};

export type Permit2 = {
  permitted: {
    token: string;
    amount: string | unknown;
  };
  spender: string;
  nonce: string | unknown;
  deadline: string | unknown;
};

export type Transaction = {
  address: string;
  abi: Abi | readonly unknown[];
  functionName: ContractFunctionName<
    Abi | readonly unknown[],
    "payable" | "nonpayable"
  >;
  args: ContractFunctionArgs<
    Abi | readonly unknown[],
    "payable" | "nonpayable",
    ContractFunctionName<Abi | readonly unknown[], "payable" | "nonpayable">
  >;
};

Using the command

In this example we will use two nested transactions. If your function requires a permit2 signature use PERMIT2_SIGNATURE_PLACEHOLDER_{n} with the index of the permit2 object in the transaction array.

Additionally if you introduce a new ERC20 token we will automatically approve the permit2 contract to spend the tokens.

If you need setApprovalForAll or are working with NFTs please reach out.

app/page.tsx

import { MiniKit } from '@worldcoin/minikit-js'
import DEXABI from "../../abi/DEX.json";
    
    // ...  
   const sendTransactionCommand = () => {
        const deadline = Math.floor(
            (Date.now() + 30 * 60 * 1000) / 1000
        ).toString();

        // transfers can also be at most 1 hour in the future.
        const permitTransfer = {
            permitted: {
                token: testTokens.optimism.USDCE,
                amount: "10000",
            },
            nonce: Date.now().toString(),
            deadline,
        };
        const permitTransferArgsForm = [
            [permitTransfer.permitted.token, permitTransfer.permitted.amount],
            permitTransfer.nonce,
            permitTransfer.deadline,
        ];

        const permitTransfer2 = {
            permitted: {
                token: testTokens.optimism.USDCE,
                amount: "20000",
            },
            nonce: deadline,
            deadline,
        };

        const permitTransferArgsForm2 = [
            [permitTransfer2.permitted.token, permitTransfer2.permitted.amount],
            permitTransfer2.nonce, permitTransfer2.deadline,
        ];

        const transferDetails = {
            to: "0x126f7998Eb44Dd2d097A8AB2eBcb28dEA1646AC8",
            requestedAmount: "10000",
        };

        const transferDetails2 = {
            to: "0x126f7998Eb44Dd2d097A8AB2eBcb28dEA1646AC8",
            requestedAmount: "20000",
        };

        const transferDetailsArgsForm = [
            transferDetails.to,
            transferDetails.requestedAmount,
        ];

        const transferDetailsArgsForm2 = [
            transferDetails2.to,
            transferDetails2.requestedAmount,
        ];

        const payload = MiniKit.commands.sendTransaction({
            transaction: [
                {
                    address: "0x34afd47fbdcc37344d1eb6a2ed53b253d4392a2f",
                    abi: DEXABI,
                    functionName: "signatureTransfer",
                    args: [
                        permitTransferArgsForm,
                        transferDetailsArgsForm,
                        "PERMIT2_SIGNATURE_PLACEHOLDER_0",
                    ],
                },
                {
                    address: "0x34afd47fbdcc37344d1eb6a2ed53b253d4392a2f",
                    abi: DEXABI,
                    functionName: "signatureTransfer",
                    args: [
                        permitTransferArgsForm2,
                        transferDetailsArgsForm2,
                        "PERMIT2_SIGNATURE_PLACEHOLDER_1",
                    ],
                },
            ],
            permit2: [
                {
                    ...permitTransfer,
                    spender: "0x34afd47fbdcc37344d1eb6a2ed53b253d4392a2f",
                },
                {
                    ...permitTransfer2,
                    spender: "0x34afd47fbdcc37344d1eb6a2ed53b253d4392a2f",
                },
            ],
        });
  };

Receiving the response

The transactionw will be first simulated and checked for errors. If there are no errors the user will be prompted to sign the transaction. The response does not wait until the transaction is mined. Thus, it's critical to confirm the transaction in your backend.

Here is the example transaction success response payload. Note that the transaction_status is submitted and not mined and the transaction_id needs to be exchanged for the actual transaction_hash

export type MiniAppSendTransactionSuccessPayload = {
    status: "success";
    transaction_status: "submitted";
    transaction_id: string;
    reference: string;
    from: string;
    chain: Network;
    timestamp: string;
    version: number;
};

app/page.tsx

import { MiniKit, MiniAppSendTransactionPayload, ResponseEvent } from '@worldcoin/minikit-js'

    useEffect(() => {
        if (!MiniKit.isInstalled()) {
            return;
        }

        MiniKit.subscribe(
            ResponseEvent.MiniAppSendTransaction,
            async (payload: MiniAppSendTransactionPayload) => {
                  if (response.status == "success") {
                    const res = await fetch(`/api/confirm-transaction`, {
                        method: "POST",
                        headers: { "Content-Type": "application/json" },
                        body: JSON.stringify(response),
                    });
                    const transaction = await res.json();
                    if (transaction.success) {
                        // Congrats your payment was successful!
                    }
                }

            }
        );
        return () => {
            MiniKit.unsubscribe(ResponseEvent.MiniAppPayment);
        };
    }, []);

Verifying the transaction

You can query for the transaction hash and status of the transaction in the developer portal. Make sure to specify type=transaction in the query string.

Transactions are sent via our relayer currently and so we provide you an internal id rather than a hash in the original response above.

app/confirm-transaction/route.ts

import { MiniAppSendTransactionSuccessPayload } from "@worldcoin/minikit-js";
import { NextRequest, NextResponse } from "next/server";

interface IRequestPayload {
  payload: MiniAppSendTransactionSuccessPayload;
}

export async function POST(req: NextRequest) {
  const { payload } = (await req.json()) as IRequestPayload;
  
  
    const response = await fetch(
      `https://developer.worldcoin.org/api/v2/minikit/transaction/${payload.transaction_id}?app_id=${process.env.APP_ID}&type=transaction`,
      {
        method: "GET",
        headers: {
          Authorization: `Bearer ${process.env.DEV_PORTAL_API_KEY}`,
        },
      }
    );
    const transaction = await response.json();

    return NextResponse.json(transaction);

  }

Example response from /minikit/transaction

{
    "transactionId": "0xa5b02107433da9e2a450c433560be1db01963a9146c14eed076cbf2c61837d60",
    "transactionHash": "0xa8388148b630b49a3d5a739eaad9e98b5766235cdb21a5ec8d3f89053d982a71",
    "transactionStatus": "failed",
    "miniappId": "app_staging_5748c49d2e6c68849479e0b321bc5257",
    "updatedAt": "2024-09-09T15:18:25.320Z",
    "network": "optimism",
    "fromWalletAddress": "0x2321401e6a175a7236498ab66f25cd1db4b17558",
    "toContractAddress": "0x2321401e6a175a7236498ab66f25cd1db4b17558"
}