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

# Custody and signing flows

> Run the enter, sign, and submit loop from a backend custody or HSM signer instead of a client wallet.

A custody or HSM-backed integration runs the whole loop server-side: your backend builds actions under your project's API key, validates each transaction before it reaches a signer, has your custody platform or HSM sign it, and reports the result back to Yield.xyz. It does not document any custodian's own API.

## Prerequisites

* A backend with your Yield.xyz project API key and a mapping from your internal account or user identifier to the address your custody platform controls for the target chain.
* A custody, HSM, or MPC signer capable of either returning a signed transaction for you to broadcast, or signing and broadcasting itself.

## The pattern

**1. Build the action under your project key**

Call `POST /v1/actions/enter` (or `/exit`, `/manage`) with the `yieldId`, the custody-controlled `address`, and arguments built from `mechanics.arguments.enter.fields`. See [Discover yields](/guides/earn/discover-yields) and [Enter and exit flows](/guides/earn/enter-exit-flows). Log the resulting `action.id` and each `transaction.id` against your own internal account or request identifier before doing anything else with them; this is the row your audit trail and any later reconciliation hangs off.

**2. Validate before it reaches the signer**

Run policy checks appropriate to your custody setup, and validate the transaction's structure with Shield before it's handed to a signer, so a malformed or unexpected `unsignedTransaction` never reaches custody:

```typescript theme={null}
import { Shield } from "@yieldxyz/shield";

const shield = new Shield();
const result = shield.validate({ unsignedTransaction: tx.unsignedTransaction, yieldId, userAddress, args });
if (!result.isValid) throw new Error(`Invalid transaction: ${result.reason}`);
```

Shield also ships as a standalone binary, callable from any language over stdin and stdout, for a signing process that isn't Node. See [Shield integration](/documentation/security/shield/integration).

**3. Sign, broadcast, and report**

Two shapes are common, depending on what your custody platform returns:

* **Custody signs, you broadcast:** decode `unsignedTransaction` for the chain family (see [Transaction signing](/guides/transaction-signing/index)), have custody sign it, broadcast through your own infrastructure, then report the hash with `PUT /v1/transactions/{transactionId}/submit-hash`.
* **Custody signs and returns a ready-to-submit payload:** hand the signed transaction to Yield.xyz instead with `POST /v1/transactions/{transactionId}/submit`, and let it broadcast.

Use one path per transaction, never both. Sign and submit `transactions[]` in `stepIndex` order, waiting for each to confirm before the next, unless `executionPattern` is `batch`.

**4. Poll to a terminal status**

Poll `GET /v1/transactions/{transactionId}` (or the whole action) to a terminal status. Record the terminal status and hash against the same internal identifier from step 1, closing the audit trail entry you opened when the action was built.

## Idempotency

`POST /v1/actions/enter` (and `/exit`, `/manage`) is not idempotent: calling it again mints a new action with new transaction ids, it does not return the one already in flight. Never re-request an action for the same intent while a prior one may already have broadcast; a signed transaction that later confirms on-chain after you requested a second one can result in the position being entered or exited twice.

If your process loses track of an in-flight action (a crash or a timeout), recover it instead of re-requesting:

* `GET /v1/actions/{actionId}`, if you logged the id, to read its transactions and their statuses and hashes.
* Poll rather than assume: an action with no progress for a period becomes `STALE`, which is not a final status. Check what was actually broadcast before deciding whether to build a new action; see the STALE row in [Enter and exit flows](/guides/earn/enter-exit-flows#errors-that-block-the-flow).

## Address mapping and audit trail

Keep the mapping between your internal account identifier and the custody-controlled `address` you pass to Yield.xyz outside of Yield.xyz itself; the API has no concept of your account structure. Log, per request: the internal identifier, the `action.id`, each `transaction.id` and its `stepIndex`, the signed payload or the hash you submitted, and the terminal status you polled. That log is what lets you reconcile a custody platform's own transaction history against Yield.xyz's without re-deriving anything from on-chain state.

Transaction broadcast, confirmation, and failure, and each action's `SUCCESS` or `FAILED` status once it settles on chain, are also pushed as [webhooks](/guides/dashboards/webhooks) if you'd rather not poll every entry in your audit log; see [Webhooks](/documentation/advanced-setup/webhooks) for the event model.

## Next steps

> **[Transaction signing](/guides/transaction-signing/index)**
>
> Decode unsignedTransaction by chain family before it reaches a signer.

> **[Webhooks](/guides/dashboards/webhooks)**
>
> Get transaction and position status changes pushed instead of polled.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.