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

# Collect approval signatures

> Collect multisig signatures from approvers' Para wallets in a React app

export const PermissionsCards = ({basePath, pages, cols = 2, horizontal = false}) => {
  const ImageCard = Card;
  const hrefOverrides = {
    "/v3/react/guides/permissions": {
      "conditional-approvals": "/v3/react/guides/conditional-approvals"
    },
    "/v3/rest/permissions": {
      "backend": "/v3/rest/permissions-administration"
    }
  };
  const guardrailBases = ["/v3/rest/permissions", "/v3/server/guides/permissions"];
  const ownership = guardrailBases.includes(basePath) ? 'guardrail' : 'request';
  const overviewImages = {
    "/v3/rest/permissions": {
      "imgUrl": "/images/v3/cards/rest-guardrails.png",
      "horizontalImgUrl": "/images/v3/cards/rest-guardrails-horizontal.png"
    },
    "/v3/server/guides/permissions": {
      "imgUrl": "/images/v3/cards/sdk-server-guardrail-signing.png",
      "horizontalImgUrl": "/images/v3/cards/sdk-server-guardrail-signing-horizontal.png"
    },
    "/v3/react/guides/permissions": {
      "imgUrl": "/images/v3/cards/sdk-get-policy-consent-url.png",
      "horizontalImgUrl": "/images/v3/cards/sdk-get-policy-consent-url-horizontal.png"
    },
    "/v3/react-native/guides/permissions": {
      "imgUrl": "/images/v3/cards/sdk-get-policy-consent-url.png",
      "horizontalImgUrl": "/images/v3/cards/sdk-get-policy-consent-url-horizontal.png"
    }
  };
  const registry = {
    overview: {
      title: "Permissions overview",
      description: "Return to the start of the Permissions path.",
      ...overviewImages[basePath]
    },
    "developer-portal": {
      title: "Create policies in the Developer Portal",
      description: "Build a policy with the Portal forms and activate it on a beta key.",
      imgUrl: "/images/v3/cards/developer-portal-policy-form.png",
      horizontalImgUrl: "/images/v3/cards/developer-portal-policy-form-horizontal.png"
    },
    "json": {
      title: "Write policy JSON",
      description: "Move to JSON for policies the forms cannot express.",
      imgUrl: "/images/v3/cards/developer-portal-policy-json.png",
      horizontalImgUrl: "/images/v3/cards/developer-portal-policy-json-horizontal.png"
    },
    "testing": {
      title: "Test your policies",
      description: "Exercise every rule on beta before you ship.",
      imgUrl: "/images/v3/cards/concept-policy-testing.png",
      horizontalImgUrl: "/images/v3/cards/concept-policy-testing-horizontal.png"
    },
    "production": {
      title: "Go to production",
      description: "Request a production review and publish your policy.",
      imgUrl: "/images/v3/cards/concept-policy-publication.png",
      horizontalImgUrl: "/images/v3/cards/concept-policy-publication-horizontal.png"
    },
    "conditional-approvals": {
      title: "Conditional approvals",
      description: "Require people to approve a request before it signs.",
      imgUrl: "/images/v3/cards/portal-conditional-approval-case.png",
      horizontalImgUrl: "/images/v3/cards/portal-conditional-approval-case-horizontal.png"
    },
    "approval-signatures": {
      title: "Collect approval signatures",
      description: "Collect signatures from approvers for multisig workflows.",
      imgUrl: "/images/v3/cards/sdk-get-approval-signatures.png",
      horizontalImgUrl: "/images/v3/cards/sdk-get-approval-signatures-horizontal.png"
    },
    "adjustable-parameters": {
      title: "Adjustable parameters",
      description: "Let users set their own limits and managers adjust approval settings.",
      imgUrl: "/images/v3/cards/portal-user-policy-limits.png",
      horizontalImgUrl: "/images/v3/cards/portal-user-policy-limits-horizontal.png"
    },
    "lifecycle": {
      title: "Manage the policy lifecycle",
      description: "Version, activate, replace and retire policies.",
      imgUrl: "/images/v3/cards/developer-portal-policy-versions.png",
      horizontalImgUrl: "/images/v3/cards/developer-portal-policy-versions-horizontal.png"
    },
    "backend": {
      title: "Manage permissions from your backend",
      description: "Assign approvers and read policy state with your server secret.",
      imgUrl: "/images/v3/cards/rest-permissions-administration.png",
      horizontalImgUrl: "/images/v3/cards/rest-permissions-administration-horizontal.png"
    },
    templates: ownership === "guardrail" ? {
      title: "Guardrail templates",
      description: "Start from a tested Guardrail policy.",
      imgUrl: "/images/v3/cards/concept-guardrail-templates.png",
      horizontalImgUrl: "/images/v3/cards/concept-guardrail-templates-horizontal.png"
    } : {
      title: "Request templates",
      description: "Start from a tested Request policy.",
      imgUrl: "/images/v3/cards/concept-request-templates.png",
      horizontalImgUrl: "/images/v3/cards/concept-request-templates-horizontal.png"
    },
    "guardrail:templates/spending-caps": {
      title: "Spending caps",
      description: "Cap native and token transfers per transaction or per day.",
      imgUrl: "/images/v3/cards/concept-spending-limits.png",
      horizontalImgUrl: "/images/v3/cards/concept-spending-limits-horizontal.png"
    },
    "guardrail:templates/allowlists": {
      title: "Recipient and contract allowlists",
      description: "Limit transfers and contract calls to addresses you know.",
      imgUrl: "/images/v3/cards/concept-recipient-allowlists.png",
      horizontalImgUrl: "/images/v3/cards/concept-recipient-allowlists-horizontal.png"
    },
    "guardrail:templates/approval-tiers": {
      title: "Approval thresholds and tiers",
      description: "Hold large transfers for approval, or deny the largest.",
      imgUrl: "/images/v3/cards/concept-approval-tiers.png",
      horizontalImgUrl: "/images/v3/cards/concept-approval-tiers-horizontal.png"
    },
    "guardrail:templates/treasury": {
      title: "Treasury approvals",
      description: "Deny unplanned actions and approve wallet or Safe payouts in stages.",
      imgUrl: "/images/v3/cards/concept-treasury-approvals.png",
      horizontalImgUrl: "/images/v3/cards/concept-treasury-approvals-horizontal.png"
    },
    "guardrail:templates/message-signing": {
      title: "Message and raw signing",
      description: "Block or hold raw signatures, and block permits.",
      imgUrl: "/images/v3/cards/concept-signing-actions.png",
      horizontalImgUrl: "/images/v3/cards/concept-signing-actions-horizontal.png"
    },
    "request:templates/allowances": {
      title: "Transfer allowances",
      description: "Let small transfers and a daily budget sign without a prompt.",
      imgUrl: "/images/v3/cards/concept-spending-limits.png",
      horizontalImgUrl: "/images/v3/cards/concept-spending-limits-horizontal.png"
    },
    "request:templates/recipients-and-contracts": {
      title: "Known recipients and app contracts",
      description: "Cover transfers to known addresses and your contract's calls.",
      imgUrl: "/images/v3/cards/concept-recipient-allowlists.png",
      horizontalImgUrl: "/images/v3/cards/concept-recipient-allowlists-horizontal.png"
    },
    "request:templates/cosigner-approval": {
      title: "Co-signer approval",
      description: "Send large transfers to a co-signer for approval.",
      imgUrl: "/images/v3/cards/portal-conditional-approval-case.png",
      horizontalImgUrl: "/images/v3/cards/portal-conditional-approval-case-horizontal.png"
    },
    "request:templates/adjustable-limits": {
      title: "User-adjustable limits",
      description: "Let each user choose their own spending limits.",
      imgUrl: "/images/v3/cards/portal-user-policy-limits.png",
      horizontalImgUrl: "/images/v3/cards/portal-user-policy-limits-horizontal.png"
    },
    "request:templates/message-signing": {
      title: "Message signing",
      description: "Sign messages from users' wallets without a prompt.",
      imgUrl: "/images/v3/cards/concept-signing-actions.png",
      horizontalImgUrl: "/images/v3/cards/concept-signing-actions-horizontal.png"
    }
  };
  const entryFor = slug => registry[slug] || registry[`${ownership}:${slug}`];
  const hrefFor = slug => {
    if (slug === 'overview') return basePath;
    const overrides = hrefOverrides[basePath] || ({});
    return overrides[slug] || `${basePath}/${slug}`;
  };
  return <CardGroup cols={cols}>
      {pages.map(slug => {
    const entry = entryFor(slug);
    return <ImageCard key={slug} title={entry.title} description={entry.description} href={hrefFor(slug)} imgUrl={horizontal ? entry.horizontalImgUrl : entry.imgUrl} horizontal={horizontal} />;
  })}
    </CardGroup>;
};

export const Card = ({imgUrl, compactImgUrl, title, description, href, horizontal = false, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  const handleClick = e => {
    e.preventDefault();
    if (newTab) {
      window.open(href, '_blank', 'noopener,noreferrer');
    } else {
      window.location.href = href;
    }
  };
  return <div className={`not-prose relative my-2 p-[1px] rounded-xl transition-all duration-300 ${isHovered && href ? 'bg-gradient-to-r from-[#FF4E00] to-[#874AE3]' : 'bg-gray-200'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <a href={href} onClick={href ? handleClick : undefined} className={`not-prose flex ${horizontal ? 'flex-row' : 'flex-col'} font-normal h-full bg-white overflow-hidden w-full ${href ? 'cursor-pointer' : 'cursor-default'} rounded-[11px] no-underline`}>
        {imgUrl && <div className={`relative overflow-hidden flex-shrink-0 ${horizontal ? 'w-[30%] rounded-l-[11px]' : 'w-full'}`} onClick={e => e.stopPropagation()}>
            <picture style={{
    display: 'contents'
  }}>
              {compactImgUrl && <source media="(max-width: 639px)" srcSet={compactImgUrl} />}
              <img src={imgUrl} alt={title} className="w-full h-full object-cover pointer-events-none select-none" draggable="false" />
            </picture>
            <div className="absolute inset-0 pointer-events-none" />
          </div>}
        <div className={`flex-grow px-6 py-5 ${horizontal ? 'w-[70%]' : 'w-full'} flex flex-col ${horizontal && imgUrl ? 'justify-center' : 'justify-start'}`}>
          {title && <h2 className="font-semibold text-base text-gray-800 m-0">{title}</h2>}
          {description && <div className={`font-normal text-gray-500 re leading-6 ${horizontal || !imgUrl ? 'mt-0' : 'mt-1'}`}>
              <p className="m-0 text-xs">{description}</p>
            </div>}
        </div>
      </a>
    </div>;
};

In a React app, the initiator and each signer act from their own signed-in session. The examples call the Para client from `useClient()`. `@getpara/react-sdk` also exports the hooks `useCreateApprovalSignatureCollection`, `useSignApprovalTransaction`, `useApprovalSigningPayload`, `useSubmitApprovalSignature` and `useApprovalSignatures`. The signer role and wallet bindings come from your backend.

Collect wallet signatures from approvers for a transaction that needs several signers, such as a Safe transaction. Para checks each signer against the policy, gathers their signatures and returns them to the person who started the collection, who assembles and submits the final transaction.

<Info>
  Signature collection is a Request feature for user-owned wallets. Build and test it with a **Development** key on Para's beta environment, and publish the policy to production through a production review.
</Info>

## How signature collection differs

A signature collection is a conditional approval case whose requirement has `evidence: "transaction_signature"`. Cases, stages, eligibility and expiry work the same way as other approvals, with these differences:

| | Approvals | Signature collections |
| - | - | - |
| Policy type | Guardrail or Request | Request only. A Guardrail with this evidence fails with `guardrail_signature_collection_invalid`. |
| How a case opens | A signing request matches the rule | Your app calls `createApprovalSignatureCollection` |
| What approvers contribute | An approval from `approveApprovalCase` | A signature from their own Para wallet |
| User consent | Required for the rule to apply | Not required |
| Initiator | Cannot decide | Can sign if eligible |
| Result | The held request can sign | The initiator retrieves the signatures |

Para does not combine the signatures, sign the final transaction or broadcast it. Your app does that, and the onchain rules, such as a Safe's owners and threshold, are separate from the roles in your policy.

## Add the requirement

Add a rule with `evidence: "transaction_signature"` to a Request policy. The rule must match the transaction you collect signatures for, the same way it would match a signing request.

For a Safe transaction in the `safe_eip712` format, Para reads the call inside it. A Safe payment of the chain's native token matches `transfer_native`, and an ERC-20 `transfer` or `transferFrom` matches `transfer_tokens`. Facts such as `request.to` and `request.value.baseUnits` then describe that inner payment. A Safe transaction that uses DelegateCall (`operation` `1`) matches `execute_safe_transaction` instead, and an inner call that is not one of these payments matches neither transfer action.

```json theme={null}
{
  "id": "payouts-need-cosigners",
  "effect": "require_approval",
  "actions": ["transfer_native"],
  "when": { "kind": "compare", "fact": "request.value.baseUnits", "op": "gt", "value": "0" },
  "approvalRequirement": {
    "id": "cosigners",
    "evidence": "transaction_signature",
    "stages": [
      { "id": "owners", "requiredApprovals": 2, "eligibility": [{ "kind": "role", "name": "safe-owner" }] }
    ]
  }
}
```

Set `requiredApprovals` so that the collection gathers at least as many signatures as the onchain threshold needs. Then assign the role to each signer and bind their wallets to the scope from your backend, as described in conditional approvals.

## Start a collection

The initiator starts a collection from a signed-in session. The wallet must belong to the initiator, be connected to your app and be bound to an authorization scope.

```ts theme={null}
const { caseId, status, expiresAt } = await para.createApprovalSignatureCollection({
  requestId: `payout-${payoutId}`,
  walletId,
  transaction: {
    format: "safe_eip712",
    typedDataJson: JSON.stringify(safeTransactionTypedData),
    chainId: "11155111",
  },
});
```

`requestId` is your own identifier for the collection, from 1 to 128 characters. `transaction` takes one of these formats:

| `format` | Fields | Wallet type |
| - | - | - |
| `safe_eip712` | `typedDataJson` with `primaryType: "SafeTx"`, `chainId` | EVM |
| `evm_typed_data` | `typedDataJson`, `chainId` | EVM |
| `evm_transaction` | `transactionBase64`, `chainId` | EVM |
| `solana_transaction` | `transactionBase64` | Solana |
| `cosmos_transaction` | `signMode` (`DIRECT` or `AMINO`), `signDocBase64` | Cosmos |
| `stellar_xdr` | `transactionXdr`, `networkPassphrase` | Stellar |
| `sui_transaction` | `transactionBase64` | Sui |

`chainId` is a decimal string. If no rule with `transaction_signature` evidence matches, the call fails with `400` and `no transaction signature requirement applies to this request`. If a rule denies the transaction, it fails with `POLICY_DENIED`.

## Collect signatures

Each signer adds a signature from their own Para wallet. The wallet must belong to the signer, match the wallet type of the format and be bound to the same scope as the collection.

<CodeGroup>
  ```ts SDK theme={null}
  await para.signApprovalTransaction({ caseId, walletId: signerWalletId });
  ```

  ```tsx React hooks theme={null}
  import { useSignApprovalTransaction } from "@getpara/react-sdk";

  export function SignApprovalButton({ caseId, walletId }: { caseId: string; walletId: string }) {
    const { signApprovalTransaction } = useSignApprovalTransaction();

    return <button onClick={() => signApprovalTransaction({ caseId, walletId })}>Sign</button>;
  }
  ```
</CodeGroup>

`signApprovalTransaction` reads the signing payload for the case, signs it with the signer's wallet and submits the signature. To show the payload before the signer confirms, read it first with `getApprovalSigningPayload(caseId)`, then sign and send the result with `submitApprovalSignature(caseId, { walletId, signature })`. EVM signatures are `0x`-prefixed hex.

Signers can also use the Portal. `getApprovalCaseReviewUrl({ caseId })` returns a link to a page titled **Sign transaction approval**, where the signer picks a **Signing wallet** and selects **Sign approval**. The page then shows **Signature recorded.**

<Frame>
  <img src="https://mintcdn.com/getpara/og2N3eReFZeUrwV7/images/v3/permissions/portal-approval-case-signature.png?fit=max&auto=format&n=og2N3eReFZeUrwV7&q=85&s=0177d162250677d38d1a442242c4e47f" alt="Para Portal page titled Sign transaction approval with the signing wallet identifier masked and the Sign approval button" width="872" height="892" data-path="images/v3/permissions/portal-approval-case-signature.png" />
</Frame>

A signer can refuse instead. **Reject** on the Portal page, or `rejectApprovalCase(caseId)` in your app, ends the collection as `rejected`, the same way one rejection ends any other case.

Para does not notify signers. Send them the case ID or the Portal link through your own app. To withdraw a collection that is still pending, the initiator calls `cancelApprovalCase(caseId)`, and the case ends as `cancelled`.

## Retrieve the signatures

When the case is `completed`, the initiator reads the signatures:

```ts theme={null}
const { transaction, payloadDigest, signatures } = await para.getApprovalSignatures(caseId);
```

Each entry in `signatures` has the signer's `walletId`, the `signature` and the signer's `actorId`. The response also repeats the `transaction`, `payloadDigest` and `signingPayloadBase64` the signers signed.

* Only the user who started the collection can read the signatures.
* The call returns `409` until the case is `completed`.
* Para checks the current policies again before it returns the signatures. If they no longer allow the transaction, the call returns `403` with `completed signature collection is denied by current policy`, and you start a new collection.
* A policy change ends a collection only when its own policy version stops applying, for example because it was replaced or turned off. The collection then becomes `invalidated` with `requirement_unavailable`. Changes to other policies leave it open, subject to the check above.

## Assemble and submit the transaction

Combine the signatures in the form your multisig expects and submit the transaction yourself. For a Safe, map each `walletId` to its owner address, order the signatures the way the Safe contract requires and call `execTransaction`.

<Warning>
  A completed collection does not mean the transaction succeeded onchain. If a signer is not an owner of the multisig, or the threshold is higher than the number of signatures, the onchain call fails even though Para completed the case.
</Warning>

<CardGroup cols={2}>
  <Card imgUrl="/images/v3/cards/concept-approval-requirements.png" title="Approval requirements reference" description="Evidence types, stages and eligibility for every requirement." href="/v3/references/permissions/approval-requirements" />

  <Card imgUrl="/images/v3/cards/concept-error-handling.png" title="Permissions errors" description="Codes and messages for refused collections and signatures." href="/v3/references/permissions/errors" />
</CardGroup>

<PermissionsCards basePath="/v3/react/guides/permissions" pages={["adjustable-parameters", "backend"]} cols={2} />


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