Skip to content

MarkoKey/with-wcpay

Repository files navigation

Turnkey Demo: WalletConnect Pay

Introduction

This repository features a mobile wallet demo powered by Turnkey that integrates with WalletConnect Pay for merchant payments. Behind the scenes, it uses @turnkey/react-native-wallet-kit for embedded wallet management and signing, and @walletconnect/pay for the payment protocol.

With Turnkey, the wallet handles authentication and EIP-712 signing — WalletConnect Pay handles everything else: transaction construction, gas sponsorship (via 7702 paymaster), and on-chain broadcast. The wallet never holds or spends native ETH for gas.

Each end-user's wallet is fully self-custodial: Turnkey creates a dedicated sub-organization per user with a 1-of-1 root quorum, meaning only the authenticated user can authorize signing. The parent organization cannot access sub-organization private keys. Turnkey acts as the secure wallet infrastructure — key generation and signing happen within Turnkey's secure enclaves, but control belongs entirely to the end-user.

Demo

wcpay-turnkey-demo.mp4

Getting started

Make sure you have Node.js installed locally; we recommend using Node v16+.

$ node --version # v16+
$ git clone https://github.com/MarkoKey/with-wcpay.git
$ cd with-wcpay/
$ cp .env.example .env
# Fill in your Turnkey and WalletConnect credentials in .env
$ npm install
$ npx expo prebuild --platform ios
$ npx expo run:ios

To configure the demo wallet you'll need the following:

Variable Description
EXPO_PUBLIC_TURNKEY_ORGANIZATION_ID Your Turnkey organization ID from the Turnkey Dashboard
EXPO_PUBLIC_TURNKEY_API_BASE_URL Turnkey API base URL (default: https://api.turnkey.com)
EXPO_PUBLIC_TURNKEY_AUTH_PROXY_CONFIG_ID Auth Proxy configuration ID for email OTP
EXPO_PUBLIC_TURNKEY_RPID Relying Party ID for passkey domain
EXPO_PUBLIC_APP_SCHEME Deep link scheme for the app (default: wcpaydemo)
EXPO_PUBLIC_WC_API_KEY WalletConnect Pay API key from the WalletConnect Dashboard

Technical tl;dr

TurnkeyProvider wraps the app at the root, providing auth state and a signMessage function to all screens:

<TurnkeyProvider config={TURNKEY_CONFIG} callbacks={TURNKEY_CALLBACKS}>
<AuthGate />
<StatusBar style="light" />
</TurnkeyProvider>

WalletConnectPay is instantiated as a singleton client, initialized with your WalletConnect API key:

export function getWcPayClient(): WalletConnectPay {
if (!wcPayClient) {
wcPayClient = new WalletConnectPay({
apiKey: WC_API_KEY,
});
}
return wcPayClient;

Payment actions from WC Pay are signed via Turnkey's signMessage, bridging the WC Pay RPC format to EIP-712 signatures:

export async function signWcPayAction(
action: WalletRpcAction,
signMessage: SignMessageFn,
walletAccount: any
): Promise<string> {
const { method, params } = action;
const parsedParams = JSON.parse(params);
switch (method) {
case "eth_signTypedData_v4": {
// params: [signerAddress, typedDataJSON]
// Pass raw typed data JSON to Turnkey — it handles EIP-712 hashing server-side
const typedDataJson =
typeof parsedParams[1] === "string"
? parsedParams[1]
: JSON.stringify(parsedParams[1]);
const result = await signMessage({
walletAccount,
message: typedDataJson,
addEthereumPrefix: false,
encoding: "PAYLOAD_ENCODING_EIP712",
hashFunction: "HASH_FUNCTION_NO_OP",
});
return assembleSignature(result);
}
case "personal_sign": {
// params: [messageHex, signerAddress]
const messageHex: string = parsedParams[0];
// Convert hex to UTF-8 string
let message: string;
if (messageHex.startsWith("0x")) {
const hex = messageHex.slice(2);
const bytes: number[] = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(parseInt(hex.substring(i, i + 2), 16));
}
message = String.fromCharCode(...bytes);
} else {
message = messageHex;
}
const result = await signMessage({
walletAccount,
message,
addEthereumPrefix: true,
});
return assembleSignature(result);
}
case "eth_sendTransaction":
throw new Error(
"eth_sendTransaction is not supported — WC Pay handles gas and broadcast via its 7702 paymaster."
);
default:
throw new Error(`Unsupported WC Pay RPC method: ${method}`);
}
}
/**
* Sign all WC Pay actions in order. Returns signatures array.
*/
export async function signAllWcPayActions(
actions: PayAction[],
signMessage: SignMessageFn,
walletAccount: any
): Promise<string[]> {
console.log(`[WCPay] Signing ${actions.length} action(s) with Turnkey...`);
const signatures: string[] = [];
for (const action of actions) {
const sig = await signWcPayAction(
action.walletRpc,
signMessage,

The payment screen orchestrates the full flow — fetching options, identity verification, signing, and broadcast:

with-wcpay/app/payment.tsx

Lines 112 to 130 in 1c3fbdb

if (!selectedOption || !paymentId || !ethAccount || !signMessage) return;
try {
setStep("signing");
const client = getWcPayClient();
const actionsResponse = await client.getRequiredPaymentActions({
paymentId,
optionId: selectedOption.id,
});
const actions = actionsResponse.actions || actionsResponse;
const signatures = await signAllWcPayActions(
actions,
signMessage,
ethAccount
);
setStep("confirming");

How it works

User ──▶ Email OTP ──▶ Turnkey creates sub-org + ETH wallet
  │
  ▼
Scan WC Pay QR ──▶ Fetch payment options from WC Pay
  │
  ▼
Confirm payment ──▶ Identity verification (if required)
  │
  ▼
Turnkey signs EIP-712 ──▶ WC Pay broadcasts via 7702 paymaster ──▶ ✅ On-chain
  1. User authenticates via email OTP — Turnkey creates a sub-organization with an Ethereum wallet
  2. User scans a WalletConnect Pay QR code (or enters a payment link manually)
  3. App fetches payment options from WC Pay and displays merchant info
  4. If required, user completes identity verification (Travel Rule compliance) via WC Pay WebView
  5. User confirms — Turnkey signs the payment authorization, WC Pay handles gas and broadcasts on-chain
  6. Payment confirms — success screen

Legal disclaimer

This demo is provided for testing and demonstration purposes only. It is not intended for production use. Use at your own risk.

About

A minimal WalletConnect Pay app powered by Turnkey

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors