Skip to main content

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:

JaringanChain IDKasus Penggunaan
Base Mainnet8453Produksi (uang nyata)
Base Sepolia84532Pengembangan & pengujian

Alamat Kontrakโ€‹

Anda membutuhkan tiga alamat untuk jaringan Anda:

VariabelTujuan
DIAMOND_ADDRESSKontrak protokol P2P.me
USDC_ADDRESSKontrak token USDC
SUBGRAPH_URLEndpoint 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:

Penyiapanโ€‹

Instal SDK:

npm install @p2pdotme/sdk viem

Anda membutuhkan:

  • publicClient: viem PublicClient untuk operasi baca
  • walletClient: viem WalletClient untuk 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โ€‹

TipeNilaiDeskripsi
BUY0Pengguna menerima USDC, mengirim fiat
SELL1Pengguna mengirim USDC, menerima fiat
PAY2Pengguna 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โ€‹

KodeArtiTindakan
INVALID_INPUTParameter tidak validPeriksa input Anda
VALIDATION_ERRORValidasi gagalPerbaiki format data
NETWORK_ERRORError RPC/subgraphCoba lagi dengan backoff
INSUFFICIENT_ALLOWANCEPerlu persetujuan USDCPanggil 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;
}