# Trading and gifting

Some games let one player hand an item or some currency to another — a gift, a trade, a drop for a
teammate. This is that. If your game has no player-to-player economy, you do not need it; read
[`inventory-and-catalogs.md`](inventory-and-catalogs.md) for what a single player owns and stop
there.

A transfer moves items and currency from one entity to another in a single call. It is separate
from the rest of the inventory surface because it names **two** players, not one, and is reached
as `gem.transfers` rather than through `gem.me`.

```js
await gem.transfers.initiate({
  to: { type: 'players', id: recipientId },
  items: [{ itemDefinitionId: 'iron-sword', quantity: 1 }],
  currencies: [{ currencyId: 'coins', amount: 50 }],
  message: 'for the boss fight',
});
```

## Instant is the default, and it changes how you build the UI

**By default a transfer completes immediately.** The value leaves the sender and arrives at the
recipient in the one call, with **no action required from the recipient** — no accept, no confirm,
no prompt. Build a gifting UI on the assumption that sending _is_ the transaction, not that it
opens a pending request.

`accept()` and `reject()` exist, but they are reachable **only for a transfer that resolved to
escrow** — a held transfer the recipient must claim. An instant transfer is already done and
cannot be accepted or rejected; calling either on one is an error, not a no-op. Do not write a
"the gift is waiting" flow for the default mode, because there is no waiting.

```js
// Only for a transfer that resolved to escrow:
await gem.transfers.accept(transferId);
await gem.transfers.reject(transferId);
// The sender can pull back an unclaimed escrow transfer:
await gem.transfers.cancel(transferId);
```

## The recipient is not told

**A transfer produces no event in the recipient's game.** As everywhere else, there is no push
into a sandboxed frame — the arriving gift is, by definition, a change some _other_ player made,
which is precisely the kind of change your game cannot be notified of. See
[`inventory-and-catalogs.md`](inventory-and-catalogs.md#6-when-to-re-read) for the full rule; the
short version is that the recipient sees the gift the next time their game re-reads its inventory,
so re-read after any action that could have involved another player, and design so a few seconds
of not-yet-seeing-it is harmless.

## `transfer_policy` is your game's rule, not the platform's

An item definition can carry a `transfer_policy` — soulbound, account-bound, tradeable — and the
platform honours it. But it is **your setting, declared in your config**, and it constrains an
honest client. It is not a security boundary: it stops the game you shipped from moving an item it
marked soulbound; it does not stop a modified client from attempting whatever the route allows.
Treat it as a design rule you asked the platform to enforce for you, not as protection against a
hostile client — that distinction is the same one
[`inventory-and-catalogs.md`](inventory-and-catalogs.md#14-there-is-no-client-side-integrity-boundary)
makes about the whole economy.

## The message is another player's text

A transfer can carry a short `message`, and on the receiving side **that message is text another
player wrote.** Up to a couple hundred characters of input you did not author arrive in your UI.
Render it the way you render any untrusted string — through `textContent` or your framework's
escaping — and never through `innerHTML`, and never build an `img.src` or `a.href` from it. The
platform bounds its length and does light sanitising, but the rule that keeps you safe is your own
rendering, not the platform's cleaning. This is the same population as an item's freeform
properties and a player's display name: three places outside text reaches your code.

## Errors

| What you get                                         | What happened                                                  | What to do                                                                                   |
| ---------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| The transfer is refused with the recipient not found | No such player, or not one reachable from your game            | Confirm the recipient id before sending; do not retry with the same id                       |
| A refusal naming the item's policy                   | The item is soulbound or account-bound and cannot move         | This is your own `transfer_policy` doing its job; there is nothing to retry                  |
| `accept` / `reject` refused                          | The transfer is instant (already complete) or already resolved | Only escrow transfers accept these; there is nothing pending to act on                       |
| `cancel` refused                                     | The escrow transfer was already claimed or already cancelled   | Re-read its state; the window to pull it back has closed                                     |
| The call times out                                   | Unknown — it may have moved the value                          | Do not resend; re-read both sides' inventories and the transfer's state to see what happened |

## What to read next

- [`inventory-and-catalogs.md`](inventory-and-catalogs.md) — currencies, items, shops and loot, for one player.
- [`game-economy-tutorial.md`](game-economy-tutorial.md) — build a shop, step by step.
