Payment Rules
A payment rule lets you require senders to pay a token amount when they open a thread to your inbox. This is the primary spam-prevention and monetisation mechanism in Packet.
Why payment rules exist
Without a payment rule, anyone can open a thread to you for free. With one, every new thread costs the sender a configurable amount of tokens — paid either directly to you or held in escrow until you approve the conversation.
The rule is enforced on-chain: a thread creation that doesn't include the correct payment simply fails.
Direct vs escrow
Direct payment — funds go straight to the recipient's token account when the thread is created. Simple, immediate, non-reversible.
Escrow payment — funds are held in the inbox's associated token account. Released when both parties approve, or when a time lock expires. If you reject the conversation, the sender can reclaim their tokens. This is better for high-value inboxes where you want to review threads before committing.
Setting up a payment rule
Pass payment to createInbox:
await client.createInbox({
inboxId: 1,
payment: {
amount: new BN(5_000_000), // 5 USDC (6 decimals)
mint: usdcMint,
escrowEnabled: false, // direct payment
},
});
With escrow:
await client.createInbox({
inboxId: 1,
payment: {
amount: new BN(5_000_000),
mint: usdcMint,
escrowEnabled: true, // funds held until released
// `to` must not be set when escrow is enabled
},
});
CreateInboxPaymentParams
| Field | Type | Required | Description |
|---|---|---|---|
amount | BN | Yes | Required payment per new thread |
mint | PublicKey | Yes | Token mint |
escrowEnabled | boolean | Yes | Hold in escrow or pay directly |
to | see below | No | Recipient token account (not valid with escrow) |
tokenProgram | PublicKey | No | Auto-detected from the mint owner if omitted |
to — recipient token account
When escrowEnabled is false, funds go to a token account you specify:
| Type | Description |
|---|---|
{ type: "ata"; owner?: PublicKey } | Associated token account of owner (defaults to inbox owner) |
{ type: "raw"; address: PublicKey } | An explicit token account address used as-is |
{ type: "custom"; keypair: Keypair; owner?: PublicKey } | A new token account is created with this keypair |
If to is omitted entirely, it defaults to the inbox owner's ATA for the given mint.
How senders interact with payment rules
When a sender calls createThread with a targetInbox, the SDK auto-reads the inbox's payment rule and fills in the payment accounts automatically. Senders don't need to manually specify payment in the common case:
const recipientInbox = await client.inbox(recipientInboxAddress);
const { client: thread } = await client.createThread({
to: recipientPublicKey,
targetInbox: recipientInbox.Inbox, // payment auto-filled
messageType: MessageType.Text,
content: "Hello!",
});
If the sender wants to override or disable the payment explicitly, they can pass payment directly to createThread. Passing { disable: true } skips the payment — which will cause the transaction to fail if the rule is enforced on-chain.
Editing or removing a payment rule
Use inbox.editPayment() to update the rule after the inbox has been created, or pass null to remove it entirely:
// Switch from direct to escrow
await inbox.editPayment({
payment: {
amount: new BN(5_000_000),
mint: usdcMint,
escrowEnabled: true,
},
});
// Remove payment requirement
await inbox.editPayment({ payment: null });
editPayment accepts the same payment shape as createInbox, plus null to clear.
Returns Promise<TxReceiptWithClient<InboxClient>>
The PaymentRule type
interface PaymentRule {
inner: Payment; // amount + mint + to
escrow: Escrow | null; // null for direct payments
tokenProgram: TokenProgramType;
}
interface Payment {
amount: BN;
mint: PublicKey;
to: PublicKey; // the token account address, not the owner
}
interface Escrow {
releaseSeconds: BN; // lock duration
}