This command is essential for applications that need to facilitate payments directly within the app,
enabling seamless transactions for users. At launch, WLD and USDC will be supported.Example: Enabling an e-commerce platform to allow users to purchase digital goods using cryptocurrencies,
providing a smooth checkout experience.Payments are easy to use and only have three simple steps.
Creating the transaction
Sending the command
Verifying the payment
For legal reasons, payments are not available in Indonesia and Philippines.
Payments are executed on-chain, so you’ll need an Ethereum compatible wallet.
Next, whitelist the address in the Developer Portal.
Whitelisting adds security to your mini app to prevent payments from being sent to an unauthorized addresses. Optionally you can
disable this check in the Developer Portal.
For security, it’s important you initialize and store your payment operation in the backend.
app/api/initiate-pay/route.ts
Copy
Ask AI
import { NextRequest, NextResponse } from 'next/server'export async function POST(req: NextRequest) { const uuid = crypto.randomUUID().replace(/-/g, '') // TODO: Store the ID field in your database so you can verify the payment later return NextResponse.json({ id: uuid })}
We currently support WLD and USDC payments on Worldchain. Below is the expected input for the Pay command.
Since World App sponsors the gas fee, there is a minimum transfer amount of $0.1 for all tokens.
PayCommandInput
Copy
Ask AI
// Represents tokens you allow the user to pay with and amount for eachexport type TokensPayload = { symbol: Tokens; token_amount: string;};export type PayCommandInput = { reference: string; to: string; tokens: TokensPayload[]; network?: Network; // Optional description: string;};
For convenience, we offer a public endpoint to query the current price of WLD in various currencies detailed here.
app/page.tsx
Copy
Ask AI
import { MiniKit, tokenToDecimals, Tokens, PayCommandInput } from '@worldcoin/minikit-js'const sendPayment = async () => { const res = await fetch('/api/initiate-payment', { method: 'POST', }) const { id } = await res.json() const payload: PayCommandInput = { reference: id, to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', // Test address tokens: [ { symbol: Tokens.WLD, token_amount: tokenToDecimals(1, Tokens.WLD).toString(), }, { symbol: Tokens.USDC, token_amount: tokenToDecimals(3, Tokens.USDC).toString(), }, ], description: 'Test example payment for minikit', } if (!MiniKit.isInstalled()) { return } const { finalPayload } = await MiniKit.commandsAsync.pay(payload) if (finalPayload.status == 'success') { const res = await fetch(`/api/confirm-payment`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(finalPayload), }) const payment = await res.json() if (payment.success) { // Congrats your payment was successful! } }}
We currently support WLD and USDC payments on Worldchain. Below is the expected input for the Pay command.
Since World App sponsors the gas fee, there is a minimum transfer amount of $0.1 for all tokens.
PayCommandInput
Copy
Ask AI
// Represents tokens you allow the user to pay with and amount for eachexport type TokensPayload = { symbol: Tokens; token_amount: string;};export type PayCommandInput = { reference: string; to: string; tokens: TokensPayload[]; network?: Network; // Optional description: string;};
For convenience, we offer a public endpoint to query the current price of WLD in various currencies detailed here.
app/page.tsx
Copy
Ask AI
import { MiniKit, tokenToDecimals, Tokens, PayCommandInput } from '@worldcoin/minikit-js' // ... const sendPayment = async () => { const res = await fetch('/api/initiate-payment', { method: 'POST' }); const { id } = await res.json(); const payload: PayCommandInput = { reference: id, to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", // Test address tokens: [ { symbol: Tokens.WLD, token_amount: tokenToDecimals(1, Tokens.WLD).toString(), }, { symbol: Tokens.USDC, token_amount: tokenToDecimals(3, Tokens.USDC).toString(), }, ], description: "Test example payment for minikit", }; if (MiniKit.isInstalled()) { MiniKit.commands.pay(payload); } };
Once World App receives the command, the user will be prompted to confirm the payment via a drawer. After that the app will send the payment to our relayer to be submitted on-chain.
The response does not wait until the transaction is mined. Thus, it’s critical to confirm the payment in your backend.
app/page.tsx
Copy
Ask AI
import { MiniKit, tokenToDecimals, Tokens, PayCommandInput, ResponseEvent } from '@worldcoin/minikit-js' useEffect(() => { if (!MiniKit.isInstalled()) { console.error("MiniKit is not installed"); return; } MiniKit.subscribe( ResponseEvent.MiniAppPayment, async (response: MiniAppPaymentPayload) => { if (response.status == "success") { const res = await fetch(`/api/confirm-payment`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(response), }); const payment = await res.json(); if (payment.success) { // Congrats your payment was successful! } } } ); return () => { MiniKit.unsubscribe(ResponseEvent.MiniAppPayment); }; }, []);
You should always verify the payment in your backend. Users can manipulate information in the frontend, so the
response must be verified in a trusted environment.
Web2 applications can call our Developer Portal API to get the current status of the transaction. Since payments are executed on-chain, it can take up to a few minutes to confirm.
You can choose to optimistically accept the payments once they’ve landed on-chain, or poll the endpoint to wait until it’s successfully mined.Web3 applications can verify payments by inspecting the ERC-4337 UserOperationEvent emitted during the pay operation.The TransferReference event will no longer be emitted. Instead, the reference string is SHA-256 hashed and the leftmost 10 bytes are included in the nonceKey embedded in the nonce field of UserOperationEvent.The nonceKey is a fixed-length 24-byte value constructed as:
1 byte: version
13 bytes: truncated SHA-256 hash of miniappId
10 bytes: truncated SHA-256 hash of reference
Conceptually:
Compute (h = \mathrm(\text))
Take h[0..10) (10 bytes)
Compare it against bytes nonceKey[14..24) (offset = 1 + 13) extracted from the UserOperationEvent nonce
In this example, we will show querying via Developer Portal API.
app/confirm-payment/route.ts
Copy
Ask AI
import { NextRequest, NextResponse } from 'next/server'import { MiniAppPaymentSuccessPayload } from '@worldcoin/minikit-js'interface IRequestPayload { payload: MiniAppPaymentSuccessPayload}export async function POST(req: NextRequest) { const { payload } = (await req.json()) as IRequestPayload // IMPORTANT: Here we should fetch the reference you created in /initiate-payment to ensure the transaction we are verifying is the same one we initiated const reference = getReferenceFromDB() // 1. Check that the transaction we received from the mini app is the same one we sent if (payload.reference === reference) { const response = await fetch( `https://developer.worldcoin.org/api/v2/minikit/transaction/${payload.transaction_id}?app_id=${process.env.APP_ID}`, { method: 'GET', headers: { Authorization: `Bearer ${process.env.DEV_PORTAL_API_KEY}`, }, } ) const transaction = await response.json() // 2. Here we optimistically confirm the transaction. // Otherwise, you can poll until the status == mined if (transaction.reference == reference && transaction.status != 'failed') { return NextResponse.json({ success: true }) } else { return NextResponse.json({ success: false }) } }}