> ## Documentation Index
> Fetch the complete documentation index at: https://docs.privacyboost.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Claimable transfers

# 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](/sdk/concepts/claimable-transfers) for the concept and
trust model.

<Warning>
  **Preview feature — pending external audit, and enabled per deployment.** Gift
  methods are called directly on the `PrivacyBoost` instance (`sdk` below) and
  take **positional** arguments. Gift support is advertised per deployment by the
  server's `/info` (through its gift refund-delay bounds); gate production use on
  your own review.
</Warning>

## Sending a gift

The most common case: you know the recipient's wallet address but they have not
joined Privacy Boost. Use `giftFundToWallet`. Every fund/claim/refund call
returns a `TransferResult`.

```typescript theme={null}
import { PrivacyBoost } from '@sunnyside-io/privacy-boost-react-native';

// Read the current chain head from your own RPC / chain client.
// 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 = await myRpcClient.getBlockNumber();
const refundAfterBlock = currentBlock + 50_000;

const result = await sdk.giftFundToWallet(
  '0x...token-address',
  '1000000000000000000', // 1 token, in wei
  '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. The second address argument is the 194-char privacy address,
followed by the recipient's wallet:

```typescript theme={null}
const result = await sdk.giftFund(
  '0x...token-address',
  '1000000000000000000',
  '0x04...recipient-privacy-address', // 194-char privacy address
  '0xRecipientEoaAddress',
  refundAfterBlock,
  currentBlock,
);
```

### Funding parameters

`giftFundToWallet` and `giftFund` take positional arguments, in order:

| Position | Argument                  | Type     | In              | Description                                                          |
| -------- | ------------------------- | -------- | --------------- | -------------------------------------------------------------------- |
| 1        | `tokenAddress`            | `string` | both            | Token contract address                                               |
| 2        | `amount`                  | `string` | both            | Amount in wei (smallest unit)                                        |
| —        | `recipientPrivacyAddress` | `string` | `giftFund` only | Recipient's 194-char privacy address (the ciphertext's ECDH target)  |
| 3/4      | `recipientWallet`         | `string` | both            | Recipient's Ethereum address the gift binds to (may be unregistered) |
| 4/5      | `refundAfterBlock`        | `number` | both            | Block height after which you may reclaim an unclaimed gift           |
| 5/6      | `currentBlock`            | `number` | both            | Current chain head; pre-validates the refund delay before submitting |

<Warning>
  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.
</Warning>

### Funding result

`giftFund` / `giftFundToWallet` return a `TransferResult` carrying two optional
gift fields:

```typescript theme={null}
interface TransferResult {
  requestId: string;
  txHash: string;
  fee: string;             // Fee paid (in wei)
  giftRecord?: GiftRecord; // persist this — it makes the gift refundable
  claimLink?: string;      // pbgift:v1:... — share with the recipient
}
```

## Sharing the claim link

`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:

```typescript theme={null}
const pending = await sdk.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):

```typescript theme={null}
// By index (positional):
await sdk.giftClaim(0, true);

// By stable commitment (preferred when the list may change):
await sdk.giftClaimByCGift(pending[0].cGift, true);
```

Or claim straight from a claim link, without a server lookup first:

```typescript theme={null}
await sdk.giftClaimFromLink('pbgift:v1:...', true);
```

<Info>
  The second argument, `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.
</Info>

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.

```typescript theme={null}
// Records the SDK persisted for gifts you funded:
const records = sdk.getGiftRecords();

// Refund the one at index 0 (a positional number, not a record value):
await sdk.giftRefundByRecord(0);
```

<Info>
  Gift records are what make a gift refundable. They are included in an
  [exported session](/sdk/react-native/guides/session-storage), 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, fundingTreeNumber, giftLeafIndex)`.
</Info>

## Previewing a claim link offline

Decode a claim link into a preview with no network call — useful to show the
recipient what they are about to accept. `decodeGiftLink` is synchronous and
offline, so there is no `await`:

```typescript theme={null}
const preview = sdk.decodeGiftLink('pbgift:v1:...');
console.log(preview.amount, preview.tokenId, preview.recipientWallet, preview.refundAfterBlock);
```

## Complete example

A minimal "send a gift" screen, following the error-handling pattern from the
[transfers guide](./transfers):

```tsx theme={null}
import React, { useState } from 'react';
import { View, Text, TextInput, Button, ActivityIndicator } from 'react-native';
import { PrivacyBoost, SdkError } from '@sunnyside-io/privacy-boost-react-native';

function GiftScreen({
  sdk,
  tokenAddress,
  currentBlock,
}: {
  sdk: PrivacyBoost;
  tokenAddress: string;
  currentBlock: number;
}) {
  const [recipient, setRecipient] = useState('');
  const [amount, setAmount] = useState('');
  const [claimLink, setClaimLink] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleGift = async () => {
    if (!sdk.isValidAddress(recipient)) {
      setError('Invalid wallet address');
      return;
    }

    setLoading(true);
    setError(null);

    try {
      const weiAmount = sdk.parseAmount(amount, 18);
      const result = await sdk.giftFundToWallet(
        tokenAddress,
        weiAmount,
        recipient,
        currentBlock + 50_000,
        currentBlock,
      );
      // Persist result.giftRecord so the gift stays refundable.
      if (result.giftRecord) saveGiftRecord(result.giftRecord);
      setClaimLink(result.claimLink ?? null);
    } catch (err: any) {
      if (SdkError.SignatureRejected.instanceOf(err)) return;
      setError(err?.inner?.message ?? err?.message ?? 'Gift funding failed');
    } finally {
      setLoading(false);
    }
  };

  return (
    <View style={{ padding: 16 }}>
      <TextInput
        value={recipient}
        onChangeText={setRecipient}
        placeholder="Recipient wallet address (0x...)"
        editable={!loading}
      />
      <TextInput
        value={amount}
        onChangeText={setAmount}
        placeholder="Amount (e.g. 1.0)"
        keyboardType="decimal-pad"
        editable={!loading}
      />
      {error && <Text style={{ color: 'red' }}>{error}</Text>}
      {claimLink && <Text selectable>{claimLink}</Text>}
      {loading ? (
        <ActivityIndicator />
      ) : (
        <Button title="Send gift" onPress={handleGift} disabled={!recipient || !amount} />
      )}
    </View>
  );
}
```

## Types

```typescript theme={null}
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;
}
```

<Info>
  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](/sdk/concepts/claimable-transfers)
  before considering it.
</Info>
