Skip to content
Blog

Peer-to-Peer Payments

Sometimes the payment isn’t going to you, it’s going between your users. P2P payments let one user send crypto to another while your site automatically collects a fee on each transaction. Under the hood, this produces a single Solana transaction with two transfers: one to the recipient and one to your treasury wallet. The fee is deducted from the payment amount, so the sender pays exactly what they expect.

  1. Go to the Tribe dashboard
  2. Open your site’s Settings → Payments
  3. Enter your treasury wallet address, the wallet where fees will be collected
  4. Toggle on P2P payments
  5. Set your fee rate as a percentage (e.g. 1 for 1%)
  6. Make sure you have at least one accepted token configured

The fee is calculated as amount × feeRate and rounded up to the token’s decimal precision. The recipient gets the remainder.

For example, with a 1% fee rate on a 2.5 SOL payment:

  • Fee to treasury: 2.5 × 0.01 = 0.025 SOL
  • Recipient receives: 2.5 - 0.025 = 2.475 SOL

The sender’s wallet is debited 2.5 SOL total. Both transfers happen atomically in a single transaction, so there’s no risk of one going through without the other.

import { Tribe } from "@tribecloud/sdk";
const payment = await tribe.getPaymentUrl({
mint: "SOL",
amount: "2.5",
recipientWallet: "RecipientWalletAddressHere",
memo: "Payment for design work",
});
// payment.paymentUrl — solana: URL for QR codes or deep links
// payment.id — use to verify payment status
// payment.expiresAt — expires after 10 minutes

For users with a browser wallet, sendViaWallet() handles the entire flow in one call:

const result = await tribe.sendViaWallet({
mint: "SOL",
amount: "2.5",
recipientWallet: "RecipientWalletAddressHere",
memo: "Payment for design work",
});
// result.status — "confirmed" | "pending" | "expired"
// result.txSignature — Solana transaction signature
function SendPayment() {
const [recipient, setRecipient] = useState("");
const [amount, setAmount] = useState("");
const [status, setStatus] = useState("idle");
const send = async () => {
setStatus("loading");
try {
const result = await tribe.sendViaWallet({
mint: "SOL",
amount,
recipientWallet: recipient,
});
setStatus(result.status === "confirmed" ? "confirmed" : "error");
} catch (err) {
console.error(err);
setStatus("error");
}
};
return (
<div>
<input
placeholder="Recipient wallet address"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
<input
type="number"
placeholder="Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<button onClick={send} disabled={status === "loading"}>
{status === "loading"
? "Sending..."
: status === "confirmed"
? "Sent!"
: `Send ${amount || "0"} SOL`}
</button>
</div>
);
}

P2P works with QR codes too. Just add recipientWallet to the options:

const payment = await tribe.renderPaymentQrCode({
mint: "SOL",
amount: "1.0",
recipientWallet: "RecipientWalletAddressHere",
container: document.getElementById("qr-container"),
});
// Poll for confirmation
const interval = setInterval(async () => {
const result = await tribe.verifyPayment(payment.id);
if (result.status !== "pending") {
clearInterval(interval);
console.log("Payment", result.status);
}
}, 3000);

Before showing P2P payment options, confirm the site has P2P enabled:

const config = await tribe.getPaymentConfig();
if (config.p2pPaymentsEnabled) {
// Show P2P payment UI
}