Integration Guide
Want to accept Aruvi payments in your app? Here's how to integrate.
Quick Integration
The simplest integration is a payment link:
<a href="https://app.aruvi.io/pay?to=0xYOUR_ADDRESS&amount=100">
Pay $100 with Aruvi
</a>
User clicks → Aruvi opens → They pay → Done.
JavaScript SDK
For deeper integration, use the contracts directly with ethers or viem.
Setup
npm install viem wagmi @tanstack/react-query
Connect to Contracts
import { createPublicClient, http, createWalletClient } from 'viem';
import { sepolia } from 'viem/chains';
// Contract ABIs
import { paymentGatewayABI } from './abis/PaymentGateway';
import { wrapperABI } from './abis/ConfidentialUSDCWrapper';
const PAYMENT_GATEWAY = '0xf2Dd4FC2114e524E9B53d9F608e7484E1CD3271b';
const WRAPPER = '0xf99376BE228E8212C3C9b8B746683C96C1517e8B';
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
Check Balance
async function getEncryptedBalance(address: string) {
const balance = await publicClient.readContract({
address: WRAPPER,
abi: wrapperABI,
functionName: 'balanceOfEncrypted',
args: [address],
});
return balance; // Encrypted - needs decryption
}
Send Payment
import { createInstance } from 'fhevmjs';
async function sendPayment(
to: string,
amount: bigint,
walletClient: WalletClient
) {
// 1. Initialize FHE
const fhevm = await createInstance({
networkUrl: process.env.SEPOLIA_RPC_URL || 'https://rpc.sepolia.org',
});
// 2. Encrypt the amount
const input = fhevm.createEncryptedInput(
PAYMENT_GATEWAY,
walletClient.account.address
);
input.add64(amount);
const { inputProof, handles } = input.encrypt();
// 3. Send transaction
const hash = await walletClient.writeContract({
address: PAYMENT_GATEWAY,
abi: paymentGatewayABI,
functionName: 'send',
args: [to, handles[0], inputProof],
});
return hash;
}
Listen for Payments
import { parseAbiItem } from 'viem';
// Watch for incoming payments
publicClient.watchContractEvent({
address: PAYMENT_GATEWAY,
abi: paymentGatewayABI,
eventName: 'PaymentSent',
args: {
to: YOUR_ADDRESS, // Filter to your address
},
onLogs: (logs) => {
logs.forEach(log => {
console.log('Payment received!', {
from: log.args.from,
paymentId: log.args.paymentId,
});
});
},
});
React Integration
With wagmi v2
import { useAccount, useWriteContract, useReadContract } from 'wagmi';
import { parseAbi } from 'viem';
const paymentGatewayABI = parseAbi([
'function send(address to, bytes32 encryptedAmount, bytes proof) returns (bytes32)',
'event PaymentSent(bytes32 indexed paymentId, address indexed from, address indexed to)',
]);
function PayButton({ to, amount }: { to: string; amount: bigint }) {
const { writeContract, isPending } = useWriteContract();
const handlePay = async () => {
// Encrypt amount with fhevmjs first
const { handles, inputProof } = await encryptAmount(amount);
writeContract({
address: PAYMENT_GATEWAY,
abi: paymentGatewayABI,
functionName: 'send',
args: [to, handles[0], inputProof],
});
};
return (
<button onClick={handlePay} disabled={isPending}>
{isPending ? 'Sending...' : 'Pay Now'}
</button>
);
}
FHE Provider
Wrap your app with FHE context:
import { createContext, useContext, useEffect, useState } from 'react';
import { createInstance, FhevmInstance } from 'fhevmjs';
const FhevmContext = createContext<FhevmInstance | null>(null);
export function FhevmProvider({ children }) {
const [fhevm, setFhevm] = useState(null);
useEffect(() => {
createInstance({
networkUrl: import.meta.env.VITE_SEPOLIA_RPC_URL || 'https://rpc.sepolia.org',
}).then(setFhevm);
}, []);
if (!fhevm) return <div>Loading encryption...</div>;
return (
<FhevmContext.Provider value={fhevm}>
{children}
</FhevmContext.Provider>
);
}
export function useFhevm() {
return useContext(FhevmContext);
}
Webhook Integration
For server-side notifications, index blockchain events:
Option 1: Run Your Own Indexer
// Simple event indexer
import { createPublicClient, http, webSocket } from 'viem';
const client = createPublicClient({
chain: sepolia,
transport: webSocket('wss://sepolia.infura.io/ws/v3/YOUR_KEY'),
});
client.watchContractEvent({
address: PAYMENT_GATEWAY,
abi: paymentGatewayABI,
eventName: 'PaymentSent',
onLogs: async (logs) => {
for (const log of logs) {
// Call your webhook
await fetch('https://your-api.com/webhook', {
method: 'POST',
body: JSON.stringify({
event: 'payment',
from: log.args.from,
to: log.args.to,
paymentId: log.args.paymentId.toString(),
transactionHash: log.transactionHash,
}),
});
}
},
});
Option 2: Use The Graph
Deploy a subgraph for indexed queries:
# schema.graphql
type Payment @entity {
id: ID!
from: Bytes!
to: Bytes!
paymentId: BigInt!
timestamp: BigInt!
blockNumber: BigInt!
}
Common Patterns
Checkout Flow
// 1. Create order in your database
const order = await createOrder({ items, total });
// 2. Generate payment request
const paymentUrl = `https://app.aruvi.io/pay?` +
`to=${YOUR_MERCHANT_ADDRESS}` +
`&amount=${order.total}` +
`&ref=${order.id}`;
// 3. Redirect user
window.location.href = paymentUrl;
// 4. In webhook, mark order as paid
app.post('/webhook', (req, res) => {
const { paymentId, ref } = req.body;
markOrderPaid(ref, paymentId);
});
Subscription Setup
async function createSubscription(
recipient: string,
monthlyAmount: bigint,
walletClient: WalletClient
) {
const fhevm = await createInstance({ networkUrl });
const input = fhevm.createEncryptedInput(
PAYMENT_GATEWAY,
walletClient.account.address
);
input.add64(monthlyAmount);
const { inputProof, handles } = input.encrypt();
const MONTHLY = 30 * 24 * 60 * 60; // 30 days in seconds
return walletClient.writeContract({
address: PAYMENT_GATEWAY,
abi: paymentGatewayABI,
functionName: 'createSubscription',
args: [recipient, handles[0], inputProof, BigInt(MONTHLY)],
});
}
Testing Integration
Sepolia Testnet
All development should happen on Sepolia:
const config = {
chainId: 11155111,
rpcUrl: process.env.SEPOLIA_RPC_URL || 'https://rpc.sepolia.org',
paymentGateway: '0xf2Dd4FC2114e524E9B53d9F608e7484E1CD3271b',
wrapper: '0xf99376BE228E8212C3C9b8B746683C96C1517e8B',
usdc: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
};
Get Test Tokens
Direct users to the faucet or provide test tokens programmatically.
Error Handling
try {
await sendPayment(to, amount, walletClient);
} catch (error) {
if (error.message.includes('insufficient')) {
// User needs to wrap more USDC
showError('Please wrap more USDC first');
} else if (error.message.includes('rejected')) {
// User cancelled in wallet
showError('Transaction cancelled');
} else {
// Unknown error
showError('Payment failed. Please try again.');
}
}
Next Steps
- API Reference — Full contract documentation
- Frontend SDK — React components and hooks
- Security — Best practices for integration