> ## 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.

# Co-signer approval

> Send large transfers from users' wallets to a co-signer for approval before they sign.

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>;
};

export const ConditionalApprovalsEnablement = () => <p>Para turns on conditional approvals per API key, so your beta and production keys are enabled separately. Until it does, validating or saving this policy fails with <code>permissions_v2_extension_unavailable</code>.</p>;

A co-signer adds a second person to large transfers from a user's wallet. The user starts the transfer, and someone you choose, such as a guardian or an account manager, approves it before Para signs.

<Info>
  <ConditionalApprovalsEnablement />
</Info>

## Require a co-signer for large transfers

ETH transfers on Base above 0.1 ETH wait for one person with the `cosigner` role. The Developer Portal forms cannot express approval requirements, so publish the template as JSON.

```json request-cosigner-approval.json theme={null}
{
  "id": "base-cosigner-large-transfers",
  "schemaVersion": "permissions.policy.v1",
  "version": "1",
  "selector": {
    "walletType": "EVM",
    "chainId": "8453"
  },
  "rules": [
    {
      "id": "cosigner-large-transfers",
      "effect": "require_approval",
      "actions": ["transfer_native"],
      "when": {
        "kind": "all",
        "of": [
          { "kind": "compare", "fact": "request.classification", "op": "eq", "value": "native_transfer" },
          { "kind": "compare", "fact": "request.value.baseUnits", "op": "gt", "value": "100000000000000000" }
        ]
      },
      "approvalRequirement": {
        "id": "cosigner-approval",
        "evidence": "authenticated_approval",
        "stages": [
          {
            "id": "cosigner",
            "requiredApprovals": 1,
            "eligibility": [{ "kind": "role", "name": "cosigner" }]
          }
        ]
      }
    }
  ],
  "metadata": {
    "productMode": "delegation",
    "description": "Send more than 0.1 ETH on Base after a co-signer approves"
  }
}
```

The user approves this policy on the consent screen like any other Request, and the rule applies only after they do. Then each large transfer opens an approval case, and a co-signer approves or rejects it in Para.

| Signing request | Result |
| - | - |
| Send 0.5 ETH after the user approved the policy | Needs approval from one `cosigner` |
| Send 0.5 ETH after the user chose **Manually approve each transaction instead** | The user reviews it first, then it needs approval from one `cosigner` |
| Send 0.05 ETH | Goes to the user for review, because this policy does not cover it |

The user who starts a transfer cannot approve its case, even if they hold the `cosigner` role. One rejection from a co-signer ends the case, and a case that is still open after 7 days expires.

When the case is `completed`, call the signing method again with the case ID as `resumeApprovalCaseId`. Para matches the case even if your app rebuilt the transaction with a new nonce or new gas values, and a completed case covers one signature. While the case is pending, the user can withdraw the transfer with `cancelApprovalCase` or the `useCancelApprovalCase` hook, and the case ends as `cancelled`. The conditional approvals guide for your platform shows how your app follows the case and completes the transfer.

<Note>
  An approval case that has not been used to sign is invalidated when the active policies that apply to its request change, for example when a policy is replaced, activated or turned off. An invalidated case cannot complete, so start a new request to open a new case.
</Note>

## Set up co-signers

Approvals come from Para users your backend picks, inside an authorization scope you name.

<Steps>
  <Step title="Activate the policy">
    Para accepts role assignments only for roles that an active policy declares, so publish the template first.
  </Step>

  <Step title="Assign the co-signer role">
    From your backend, give `cosigner` to each co-signer's Para user in the authorization scope.
  </Step>

  <Step title="Bind the user's wallet">
    Bind the user's wallet to the same authorization scope, so its cases go to those co-signers. Give each user their own scope if each has their own co-signer.
  </Step>
</Steps>

<Check>
  Test it on your Development key with two test users: assign `cosigner` to the second user and bind the first user's wallet. After the first user approves the consent screen, send 0.5 ETH from their wallet, approve the case as the second user, then sign again with `resumeApprovalCaseId` and check that the transfer signs.
</Check>

## Combine with an allowance

On its own, this policy leaves smaller transfers to the user's review. To let small transfers sign without a prompt as well, add the `allow-small-native-transfers` rule from the allowances templates to this policy's `rules`. The user then consents once to both rules:

| Transfer | Result |
| - | - |
| Up to 0.01 ETH | Signs without a prompt |
| More than 0.01 ETH and up to 0.1 ETH | Goes to the user for review |
| More than 0.1 ETH | Needs approval from one `cosigner` |

If an `allow` rule and a `require_approval` rule both match a transfer, the approval wins, whatever order the rules are in.

## Publish with co-signers

Roles and wallet bindings belong to each environment. After a production review, assign the production co-signers and bind production wallets before your users need large transfers there.

<CardGroup cols={1}>
  <Card title="Approval requirements reference" description="Stages, eligibility and the rules Para applies to each case." href="/v3/references/permissions/approval-requirements" imgUrl="/images/v3/cards/concept-approval-requirements-horizontal.png" horizontal />
</CardGroup>

<PermissionsCards basePath="/v3/react/guides/permissions" pages={["conditional-approvals"]} cols={1} horizontal />

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


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