SDK
Cara termudah untuk mengintegrasikan P2P Protocol ke dalam aplikasi Anda adalah melalui TypeScript SDK (@p2pdotme/sdk). SDK ini menyediakan modul siap pakai untuk pesanan, profil pengguna, penetapan harga, konfigurasi mata uang, ZK-KYC, deteksi penipuan, dan penguraian QR, serta provider React dan hooks opsional.
Agnostik framework. Inti SDK berupa TypeScript murni, dengan React hooks opsional.
Agnostik wallet. Bawa viem client Anda sendiri.
Tanpa pengecualian. Semua metode mengembalikan tipe Result / ResultAsync.
Modular. Impor hanya yang Anda butuhkan.
Instalasi:
npm install @p2pdotme/sdk
Kode sumber lengkap: https://github.com/p2pdotme/p2pdotme-sdk
Pengaturan Lingkunganโ
Jaringanโ
SDK mendukung Base (mainnet dan testnet). Pilih salah satu:
| Jaringan | Chain ID | Kasus Penggunaan |
|---|---|---|
| Base Mainnet | 8453 | Produksi (uang nyata) |
| Base Sepolia | 84532 | Pengembangan & pengujian |
Alamat Kontrakโ
Anda membutuhkan tiga alamat untuk jaringan Anda:
| Variabel | Tujuan |
|---|---|
DIAMOND_ADDRESS | Kontrak protokol P2P.me |
USDC_ADDRESS | Kontrak token USDC |
SUBGRAPH_URL | Endpoint GraphQL untuk kueri pesanan |
Base Sepolia (Testnet)โ
REACT_APP_DIAMOND_ADDRESS=0xce868398FDaDcA368EAc203222874D6888532aE2
REACT_APP_USDC_ADDRESS=0xDABa329Ed949f28F64019f22c33c3B253B2Ded60
REACT_APP_SUBGRAPH_URL=https://api.studio.thegraph.com/query/110312/indexer-one/version/latest
Base Mainnet (Produksi)โ
REACT_APP_DIAMOND_ADDRESS=0x4cad6eC90e65baBec9335cAd728DDC610c316368
REACT_APP_USDC_ADDRESS=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
REACT_APP_SUBGRAPH_URL=<deploy-your-own>
Untuk subgraph mainnet: Deploy sendiri menggunakan repositori P2P.me Subgraph.
URL RPCโ
Anda membutuhkan endpoint RPC. Pilihan tersedia:
Publik (gratis, ada batasan rate):
# Base Mainnet
REACT_APP_RPC_URL=https://mainnet.base.org
# Base Sepolia Testnet
REACT_APP_RPC_URL=https://sepolia.base.org
Direkomendasikan (komersial, lebih cepat, lebih andal):
Membuat File .env.localโ
Buat file .env.local di direktori root proyek Anda. Salin nilai-nilai dari atas sesuai jaringan yang Anda gunakan:
Untuk Base Sepolia Testnet:
# Jaringan
REACT_APP_RPC_URL=https://sepolia.base.org
REACT_APP_CHAIN_ID=84532
# Alamat Kontrak
REACT_APP_DIAMOND_ADDRESS=0xce868398FDaDcA368EAc203222874D6888532aE2
REACT_APP_USDC_ADDRESS=0xDABa329Ed949f28F64019f22c33c3B253B2Ded60
REACT_APP_SUBGRAPH_URL=https://api.studio.thegraph.com/query/110312/indexer-one/version/latest
# Akun Anda (untuk pengujian; khusus pengembangan saja!)
REACT_APP_PRIVATE_KEY=<your-private-key-here>
Untuk Base Mainnet (Produksi):
# Jaringan
REACT_APP_RPC_URL=https://mainnet.base.org
REACT_APP_CHAIN_ID=8453
# Alamat Kontrak
REACT_APP_DIAMOND_ADDRESS=0x4cad6eC90e65baBec9335cAd728DDC610c316368
REACT_APP_USDC_ADDRESS=0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
REACT_APP_SUBGRAPH_URL=<your-deployed-subgraph-url>
Muat dalam kode Anda:
const RPC_URL = import.meta.env.REACT_APP_RPC_URL;
const DIAMOND_ADDRESS = import.meta.env.REACT_APP_DIAMOND_ADDRESS;
const USDC_ADDRESS = import.meta.env.REACT_APP_USDC_ADDRESS;
const SUBGRAPH_URL = import.meta.env.REACT_APP_SUBGRAPH_URL;
Mendapatkan Dana Testnetโ
Untuk menguji pesanan SELL/PAY di Base Sepolia, Anda membutuhkan ETH dan USDC:
- Faucet.circle.com, memberikan ETH dan testnet USDC (direkomendasikan)
- SepoliaFaucet.com, ETH saja
Penyiapanโ
Instal SDK:
npm install @p2pdotme/sdk viem
Anda membutuhkan:
- publicClient: viem
PublicClientuntuk operasi baca - walletClient: viem
WalletClientuntuk operasi tulis - diamondAddress: Kontrak P2P Protocol
- usdcAddress: Alamat token USDC
- subgraphUrl: Endpoint GraphQL
Contoh Reactโ
import { SdkProvider, useOrders, useProfile } from "@p2pdotme/sdk/react";
import { createPublicClient, createWalletClient, http } from "viem";
import { baseSepolia } from "viem/chains";
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http(RPC_URL),
});
const walletClient = createWalletClient({
chain: baseSepolia,
transport: http(RPC_URL),
account: YOUR_ACCOUNT,
});
function App() {
return (
<SdkProvider
publicClient={publicClient}
diamondAddress={DIAMOND_ADDRESS}
usdcAddress={USDC_ADDRESS}
subgraphUrl={SUBGRAPH_URL}
>
<OrderFlow />
</SdkProvider>
);
}
function OrderFlow() {
const orders = useOrders();
const profile = useProfile();
async function buyUsdc() {
const result = await orders.placeOrder.execute({
walletClient,
orderType: 0, // BUY
currency: "INR",
user: userAddress,
recipientAddr: userAddress,
amount: 10_000_000n, // 10 USDC (6 decimals)
fiatAmount: 850_000_000n, // 850 INR (6 decimals)
fiatAmountLimit: 0n,
});
result.match(
({ hash, meta }) => console.log("Order placed:", hash),
(err) => console.error(`Error: ${err.code} - ${err.message}`),
);
}
return <button onClick={buyUsdc}>Buy USDC</button>;
}
Memahami Tipe Resultโ
Semua metode SDK mengembalikan tipe Result dari neverthrow (tidak pernah melempar pengecualian):
const result = await orders.placeOrder.execute(params);
// Selalu gunakan .match() untuk menangani sukses dan error
result.match(
(success) => {
// Tangani sukses
console.log("Success:", success.hash);
},
(error) => {
// Tangani error
console.error("Error:", error.code, error.message);
}
);
// Atau periksa dengan .isOk()
if (result.isOk()) {
console.log("Hash:", result.value.hash);
} else {
console.log("Error:", result.error.code);
}
Pesananโ
Modul orders menangani semua operasi siklus hidup pesanan.
Tipe Pesananโ
| Tipe | Nilai | Deskripsi |
|---|---|---|
| BUY | 0 | Pengguna menerima USDC, mengirim fiat |
| SELL | 1 | Pengguna mengirim USDC, menerima fiat |
| PAY | 2 | Pengguna mengirim USDC ke wallet |
Membuat Pesanan BUYโ
const result = await orders.placeOrder.execute({
walletClient,
orderType: 0,
currency: "INR",
user: userAddress,
recipientAddr: userAddress,
amount: 10_000_000n,
fiatAmount: 850_000_000n,
fiatAmountLimit: 0n,
});
Membuat Pesanan SELLโ
SELL membutuhkan persetujuan USDC terlebih dahulu:
// 1. Setujui
await orders.approveUsdc.execute({
walletClient,
amount: 10_000_000n,
});
// 2. Buat pesanan
const result = await orders.placeOrder.execute({
walletClient,
orderType: 1, // SELL
currency: "INR",
user: userAddress,
recipientAddr: userAddress,
amount: 10_000_000n,
fiatAmount: 850_000_000n,
fiatAmountLimit: 0n,
});
// 3. Atur tujuan pembayaran
await orders.setSellOrderUpi.execute({
walletClient,
orderId: result.value.meta.orderId,
paymentAddress: "user@upi",
});
Melacak Pesananโ
// Dapatkan semua pesanan pengguna (mengembalikan tipe Result)
const result = await orders.getOrders({
userAddress: userAddress,
limit: 20,
skip: 0,
});
result.match(
(ordersList) => {
console.log(`Found ${ordersList.length} orders`);
ordersList.forEach((order) => {
console.log(`Order ${order.orderId}: ${order.status}`);
});
},
(err) => console.error(`Error: ${err.code}`),
);
// Dapatkan satu pesanan
const singleResult = await orders.getOrder({ orderId: 42n });
singleResult.match(
(order) => console.log("Order:", order),
(err) => console.error("Error:", err),
);
// Batalkan pesanan
const cancelResult = await orders.cancelOrder.execute({
walletClient,
orderId: "0x123...",
});
cancelResult.match(
({ hash }) => console.log("Cancelled! Hash:", hash),
(err) => console.error("Error:", err.message),
);
// Ajukan sengketa
const disputeResult = await orders.raiseDispute.execute({
walletClient,
orderId: "0x123...",
});
disputeResult.match(
({ hash }) => console.log("Dispute raised! Hash:", hash),
(err) => console.error("Error:", err.message),
);
Mendapatkan Biayaโ
const result = await orders.getFeeConfig({ currency: "INR" });
result.match(
(feeConfig) => {
// Field FeeConfig adalah bigint 6 desimal
console.log(feeConfig.smallOrderThreshold, feeConfig.smallOrderFixedFee);
},
(err) => console.error("Error:", err.code),
);
Profil & Batasโ
Periksa saldo pengguna, allowance USDC, dan batas transaksi.
Memeriksa Saldoโ
// Saldo USDC
const balanceResult = await profile.getUsdcBalance({ address: userAddr });
if (balanceResult.isErr()) throw balanceResult.error;
const usdcBalance = balanceResult.value; // bigint
// Allowance USDC (sebelum SELL/PAY)
const allowance = await profile.getUsdcAllowance({
owner: userAddress,
});
// getBalances juga mengembalikan Result:
const balancesResult = await profile.getBalances({ address: userAddress, currency: "INR" });
balancesResult.match(
(b) => console.log(b.usdc, b.fiat),
(err) => console.error(err.code),
);
Memeriksa Batasโ
const result = await profile.getTxLimits({
address: userAddress,
currency: "INR",
});
result.match(
(limits) => {
// limits memiliki: buyLimit, sellLimit
console.log("Buy Limit:", limits.buyLimit);
console.log("Sell Limit:", limits.sellLimit);
},
(err) => console.error("Error:", err.code),
);
Batas bergantung pada reputasi, level KYC, dan parameter risiko mata uang.
Pemeriksaan Pra-transaksiโ
Sebelum BUY:
const result = await profile.getTxLimits({
address: userAddr,
currency: "INR",
});
result.match(
(limits) => {
if (amount > limits.buyLimit) {
console.log("Exceeds buy limit");
} else {
console.log("Amount OK");
}
},
(err) => console.error("Error:", err.code),
);
Sebelum SELL:
// Dapatkan saldo USDC
const balanceResult = await profile.getUsdcBalance({ address: userAddr });
if (balanceResult.isErr()) throw balanceResult.error;
const usdcBalance = balanceResult.value; // bigint
// Dapatkan batas
const limitsResult = await profile.getTxLimits({
address: userAddr,
currency: "INR",
});
limitsResult.match(
(limits) => {
if (usdcBalance < amount) {
console.log("Insufficient USDC");
} else if (amount > limits.sellLimit) {
console.log("Exceeds sell limit");
} else {
console.log("Can sell");
}
},
(err) => console.error("Error:", err.code),
);
Penanganan Errorโ
Dekode error kontrak menjadi pesan yang ramah pengguna.
Struktur Errorโ
const error = {
code: "INSUFFICIENT_BALANCE",
message: "User has insufficient USDC balance",
cause: rawError, // underlying error
};
Mendekode Error Kontrakโ
import {
parseContractError,
getContractErrorMessage,
} from "@p2pdotme/sdk/orders";
orders.placeOrder.execute(params).match(
({ hash }) => console.log("Placed:", hash),
(err) => {
const code = parseContractError(err.cause);
const message = getContractErrorMessage(code);
showToast(message); // "Order amount exceeds limit"
},
);
Kode Error Umumโ
| Kode | Arti | Tindakan |
|---|---|---|
INVALID_INPUT | Parameter tidak valid | Periksa input Anda |
VALIDATION_ERROR | Validasi gagal | Perbaiki format data |
NETWORK_ERROR | Error RPC/subgraph | Coba lagi dengan backoff |
INSUFFICIENT_ALLOWANCE | Perlu persetujuan USDC | Panggil approveUsdc.execute() |
Pola Penanganan Errorโ
Degradasi yang baik:
const result = await orders.placeOrder.execute(params);
result.match(
(success) => {
return { ok: true, hash: success.hash };
},
(error) => {
console.error(`Error [${error.code}]: ${error.message}`);
return { ok: false, message: error.message };
},
);
Coba ulang dengan backoff:
async function retryOrder(params, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const result = await orders.placeOrder.execute(params);
if (result.isOk()) {
return result.value;
}
const { code } = result.error;
// Hanya coba ulang pada error jaringan
if (code === "NETWORK_ERROR") {
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i)));
continue;
}
// Jangan coba ulang error validasi
throw result.error;
}
}
Pola Tingkat Lanjutโ
Gunakan pemisahan prepare/execute, relay identity, dan penyimpanan kustom.
Prepare vs Executeโ
prepare() mengembalikan transaksi mentah tanpa penandatanganan:
const result = await orders.placeOrder.prepare(params);
// Mengembalikan: { to, data, value, meta }
// Kirim melalui relayer, multisig, atau penanda tangan kustom
execute() menandatangani dan mengirim dengan viem:
const result = await orders.placeOrder.execute({
walletClient,
...params
});
Kasus Penggunaanโ
Relaying tanpa gas:
const tx = await orders.placeOrder.prepare(params);
await relayerApi.send(tx.value);
Multi-sig:
const tx = await orders.placeOrder.prepare(params);
await multiSigWallet.queue(tx.value);
Penandatanganan sisi server:
const tx = await orders.placeOrder.prepare(params);
const signed = await serverSignAndSend(tx.value);
Relay Identityโ
SDK menggunakan keypair untuk anonimitas pengirim. Default tersimpan di memori (hilang saat refresh).
Simpan ke localStorage:
import { createLocalStorageRelayStore } from "@p2pdotme/sdk/orders";
<SdkProvider
orders={{
relayIdentityStore: createLocalStorageRelayStore({ key: "relay" })
}}
/>
Penyimpanan kustom:
const store = {
get: async () => db.getRelayIdentity(),
set: async (id) => db.saveRelayIdentity(id),
};
<SdkProvider orders={{ relayIdentityStore: store }} />
Mandiri (Tanpa React)โ
Gunakan factory langsung:
import { createOrders } from "@p2pdotme/sdk/orders";
import { createProfile } from "@p2pdotme/sdk/profile";
import { createPublicClient, http } from "viem";
import { baseSepolia } from "viem/chains";
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http("https://sepolia.base.org"),
});
const orders = createOrders({
publicClient,
diamondAddress: "0xce868398FDaDcA368EAc203222874D6888532aE2",
usdcAddress: "0xDABa329Ed949f28F64019f22c33c3B253B2Ded60",
subgraphUrl: "https://api.studio.thegraph.com/query/110312/indexer-one/version/latest",
});
const profile = createProfile({
publicClient,
diamondAddress: "0xce868398FDaDcA368EAc203222874D6888532aE2",
usdcAddress: "0xDABa329Ed949f28F64019f22c33c3B253B2Ded60",
});
// Gunakan keduanya
const orderResult = await orders.getOrder({ orderId: "0x123..." });
orderResult.match(
(order) => console.log("Order:", order),
(err) => console.error("Error:", err),
);
const balanceResult = await profile.getUsdcBalance({ address: "0x..." });
if (balanceResult.isErr()) throw balanceResult.error;
console.log("USDC:", balanceResult.value); // bigint
Contohโ
Alur kerja lengkap untuk pola umum.
Alur BUYโ
async function buyUsdc(userAddr, currency, fiatAmount) {
const limitsResult = await profile.getTxLimits({ address: userAddr, currency });
if (limitsResult.isErr()) throw limitsResult.error;
if (fiatAmount > limitsResult.value.buyLimit) throw new Error("Exceeds limit");
const priceClient = createPrices({ publicClient, diamondAddress });
const priceResult = await priceClient.getPriceConfig({ currency });
if (priceResult.isErr()) throw priceResult.error;
const usdcAmount = (fiatAmount * 1_000_000n) / priceResult.value.buyPrice;
return await orders.placeOrder.execute({
walletClient,
orderType: 0,
currency,
user: userAddr,
recipientAddr: userAddr,
amount: usdcAmount,
fiatAmount,
fiatAmountLimit: 0n,
});
}
Alur SELLโ
async function sellUsdc(userAddr, currency, usdcAmount) {
const balanceResult = await profile.getUsdcBalance({ address: userAddr });
if (balanceResult.isErr()) throw balanceResult.error;
if (balanceResult.value < usdcAmount) throw new Error("Insufficient USDC");
const allowance = await profile.getUsdcAllowance({ owner: userAddr });
if (allowance < usdcAmount) {
await orders.approveUsdc.execute({ walletClient, amount: usdcAmount });
}
const priceClient = createPrices({ publicClient, diamondAddress });
const priceResult = await priceClient.getPriceConfig({ currency });
if (priceResult.isErr()) throw priceResult.error;
const fiatAmount = (usdcAmount * priceResult.value.sellPrice) / 1_000_000n;
const placed = await orders.placeOrder.execute({
walletClient,
orderType: 1,
currency,
user: userAddr,
recipientAddr: userAddr,
amount: usdcAmount,
fiatAmount,
fiatAmountLimit: 0n,
});
if (!placed.isOk()) return placed;
return await orders.setSellOrderUpi.execute({
walletClient,
orderId: placed.value.meta.orderId,
paymentAddress: "user@upi",
});
}
Memantau Status Pesananโ
function useOrderStatus(orderId) {
const [order, setOrder] = useState(null);
const orders = useOrders();
useEffect(() => {
let poll;
const fn = async () => {
const result = await orders.getOrder({ orderId });
if (result.isOk()) setOrder(result.value);
};
fn();
poll = setInterval(fn, 3000);
return () => clearInterval(poll);
}, [orderId, orders]);
return order;
}