Skip to main content

Claimable Transfers (Gifts)

Send shielded funds to an Ethereum wallet address that has not registered with Privacy Boost yet. The recipient claims by proving ownership of that address; you reclaim the funds if they never do. See Claimable Transfers for the concept and trust model.
Preview feature — pending external audit, and enabled per deployment. Gift methods are exposed on the underlying WASM SDK (sdk.wasm), so the code blocks below are illustrative and not type-checked against the published build.

Sending a gift

The most common case: you know the recipient’s wallet address but they have not joined Privacy Boost. Use giftFundToWallet.
// Refund deadline: a block height after which you can reclaim an unclaimed gift.
// The min/max delay is advertised from the server's /info endpoint.
const currentBlock = Number(await publicClient.getBlockNumber());
const refundAfterBlock = currentBlock + 50_000;

const result = await sdk.wasm.giftFundToWallet({
  tokenAddress: '0x...token-address',
  amount: '1000000000000000000', // 1 token, in wei
  recipientWallet: '0xRecipientEoaAddress',
  refundAfterBlock,
  currentBlock,
});

console.log('Funded gift:', result.txHash);
console.log('Share this claim link with the recipient:', result.claimLink);

// Persist result.giftRecord so you can refund later (see "Refunding" below).
saveGiftRecord(result.giftRecord);
If the recipient is already a Privacy Boost user, fund with their privacy address too (giftFund) so the gift ciphertext is sealed to their viewing key for the smoothest discovery:
const result = await sdk.wasm.giftFund({
  tokenAddress: '0x...token-address',
  amount: '1000000000000000000',
  recipient: '0x04...recipient-privacy-address', // 194-char privacy address
  recipientWallet: '0xRecipientEoaAddress',
  refundAfterBlock,
  currentBlock,
});

Funding parameters

ParameterTypeRequiredDescription
tokenAddressstringYesToken contract address
amountstringYesAmount in wei (smallest unit)
recipientstringgiftFund onlyRecipient’s 194-char privacy address (the ciphertext’s ECDH target)
recipientWalletstringYesRecipient’s Ethereum address the gift binds to (may be unregistered)
refundAfterBlocknumberYesBlock height after which you may reclaim an unclaimed gift
currentBlocknumberYesCurrent chain head; pre-validates the refund delay before submitting
The gift binds irrevocably to recipientWallet. A wrong-but-valid address can be claimed by whoever controls it, and only an unclaimed gift is refundable. Show the exact checksummed address and require explicit confirmation before funding.

Funding result

giftFund / giftFundToWallet return a TransferResult extended with two gift fields:
interface TransferResult {
  requestId: string;
  txHash: string;
  giftRecord?: GiftRecord; // persist this — it makes the gift refundable
  claimLink?: string;      // pbgift:v1:... — share with the recipient
}
result.claimLink is a pbgift:v1:... string that carries the encrypted opening the recipient needs. Share it out of band (message, QR code). Treat it like a password — it reveals the gift’s amount and recipient to anyone who reads it.

Claiming a gift

The recipient registers Privacy Boost with the same wallet address, then claims. List the pending gifts addressed to the authenticated wallet:
const pending = await sdk.wasm.getPendingGifts();
for (const gift of pending) {
  console.log(`#${gift.index}: ${gift.amount} of token ${gift.tokenId}`);
}
Claim one by its position in that list, or — more robustly — by its stable commitment cGift (the list can shift as gifts settle):
// By index (positional):
await sdk.wasm.giftClaim({ index: 0, acknowledgeUnknownSender: true });

// By stable commitment (preferred when the list may change):
await sdk.wasm.giftClaimByCGift({ cGift: pending[0].cGift, acknowledgeUnknownSender: true });
Or claim straight from a claim link, without a server lookup first:
await sdk.wasm.giftClaimFromLink({
  link: 'pbgift:v1:...',
  acknowledgeUnknownSender: true,
});
acknowledgeUnknownSender must be true to claim. The hidden-sender model cannot reveal who funded a gift, so the recipient explicitly acknowledges accepting funds from an unknown sender. Surface this as a consent prompt.
A successful claim re-mints the gift into a normal shielded note — from then on it behaves like any other note in the recipient’s balance.

Refunding an unclaimed gift

A claim and a refund spend the same nullifier, so only one can ever settle. After the refund deadline passes, reclaim an unclaimed gift using the local record you saved at funding time.
// Records the SDK persisted for gifts you funded:
const records = sdk.wasm.getGiftRecords();

// Refund the one at index 0:
await sdk.wasm.giftRefundByRecord(0);
Gift records are what make a gift refundable. They are included in an exported session, so persisting and restoring the session keeps your unclaimed gifts refundable across restarts. If you ever lose the record, you can still refund by supplying every field manually via giftRefund({ recipientWallet, blind, refundAfterBlock, tokenId, amount, treeNumber, leafIndex }).
Decode and decrypt a claim link into a preview without any network call — useful to show the recipient what they are about to accept:
const preview = sdk.wasm.decodeGiftLink('pbgift:v1:...');
console.log(preview.amount, preview.tokenId, preview.recipientWallet, preview.refundAfterBlock);

Types

interface GiftRecord {
  recipientWallet: string;
  blind: string;
  refundAfterBlock: number;
  tokenId: number;
  amount: string;
  cGift: string;
  giftNpk: string;
  requestId: string;
  fundingTreeNumber?: number;
  giftLeafIndex?: number;
  status?: string;
}

interface PendingGift {
  index: number;
  treeNumber: number;
  leafIndex: number;
  cGift: string;
  tokenId: number;
  amount: string;
  refundAfterBlock: number;
  recipientWallet: string;
}

interface GiftLinkPreview {
  server: string;
  chainId: number;
  cGift: string;
  recipientWallet: string;
  tokenId: number;
  amount: string;
  refundAfterBlock: number;
}
A third funding mode, giftFundSecretBearer, binds a gift to a secret instead of a wallet so it can be claimed before the recipient has any wallet. It is experimental, disabled by default, and a bearer instrument (whoever holds the link can claim). See the concept page before considering it.