From c3bb28e8bb9fa4184f1dfc10a685a22968962fa5 Mon Sep 17 00:00:00 2001 From: sednaoui Date: Tue, 22 Sep 2026 16:56:58 +0200 Subject: [PATCH 1/3] docs: clarify Safe Recovery Service APIs --- docs/wallet/recovery/1-overview.mdx | 144 +++--- docs/wallet/recovery/2-ux-api.mdx | 681 +++++++++++++++------------- docs/wallet/recovery/3-auth-api.mdx | 323 ++++++------- src/data/safeRecoveryService.ts | 36 +- 4 files changed, 620 insertions(+), 564 deletions(-) diff --git a/docs/wallet/recovery/1-overview.mdx b/docs/wallet/recovery/1-overview.mdx index bf6a485..f1a68c4 100644 --- a/docs/wallet/recovery/1-overview.mdx +++ b/docs/wallet/recovery/1-overview.mdx @@ -1,106 +1,110 @@ --- -title: An overview of Safe Recovery Service API -description: An overview of Candide's Safe Recovery Service and how it works. Features signature aggregation, gas sponsorship, recovery request monitoring, signature storage, and auto-execution. +title: Safe Recovery Service API Overview +description: Learn how Candide's Safe Recovery Service coordinates guardian recovery, sponsors transactions, sends alerts, and enables email/SMS recovery for Safe accounts. keywords: [safe recovery, social recovery, smart wallet, email recovery, guardian recovery] --- -Secure Safe Accounts with diverse recovery options including trusted contacts, email/SMS verification, Passkeys, secondary devices, and more. -The Safe Recovery service consists of two main components: [Recovery UX](#recovery-ux-api) and [Email/SMS Recovery](#emailsms-recovery-api). +Candide's Safe Recovery Service helps wallets recover Safe accounts when users lose access to their signing keys. It works with the [Social Recovery Module](/wallet/plugins/recovery-with-guardians/), which lets trusted guardians approve new account owners through a signature threshold and a time delay. + +The module enforces recovery rules on-chain. The service coordinates the process: collecting guardian signatures, submitting transactions, and notifying account owners. ## Who is this for -Wallets implementing the Safe [Social Recovery Module](/wallet/plugins/recovery-with-guardians/) that want to provide a seamless and secure recovery experience for their end users. -## Recovery UX API +Wallet developers integrating the Social Recovery Module who want to automate recovery or offer email/SMS as a recovery method. + +The service provides two APIs: + +| API | Purpose | +|-----|---------| +| [Recovery UX API](#recovery-ux-api) | Coordinate recovery requests, collect signatures, sponsor transactions, and send alerts. | +| [Email/SMS Recovery API](#emailsms-recovery-api) | Let users authorize a service-managed guardian through email or phone verification. | -### Automatic Execution -The service provides automatic execution options to minimize UX friction and enhance privacy for recovery contacts and guardians. -Configure the service to automatically execute: +## Recovery UX API -1. The recovery confirmation transaction once the signature threshold is met, eliminating the need for guardians to pay gas fees. -2. The recovery finalization transaction after the grace period expires, removing the need for recovery contacts to return and complete the process. +Use this API to manage recovery requests approved by guardians, such as trusted contacts or hardware wallets. ### Signature Aggregation and Storage -Guardian signatures can be submitted to the service for off-chain collection. Once all required signatures are collected, the service automatically executes the recovery confirmation and finalization. -### Gas Sponsorship Relayer -The service includes a gas sponsorship relayer that covers gas costs for both confirmation and finalization transactions. +The service collects and stores guardian signatures off-chain until the account's recovery threshold is met. For example, a 2-of-3 threshold requires approval from any two of the three guardians. + +### Automatic Execution and Gas Sponsorship {#automatic-execution} + +The service can be configured to submit two on-chain transactions automatically: + +1. **Execution:** Submit the approved recovery request once the guardian signature threshold is met. This starts the grace period. +2. **Finalization:** Complete the ownership change after the grace period ends. + +The gas sponsorship relayer covers the costs of these transactions, so guardians do not need to hold funds or return to finalize recovery. ### Social Engineering Protection -The API provides an emoji-based communication system that enables guardians to verify and approve legitimate recovery requests, effectively preventing social engineering attacks by malicious actors attempting to manipulate the recovery process. + +Each recovery request has an emoji sequence that guardians can check with the account owner through a trusted communication channel before signing. This helps guardians identify the intended request and reduces the risk of approving a fraudulent one. ### Alerts and Notifications -Account owners can subscribe to email or SMS notifications when a recovery request is initiated, whether onchain or through the service, ensuring they remain informed throughout the recovery process. -The service monitors transactions sent through the Safe Recovery Module, using events and tracing to index transactions and deliver timely alerts. + +Account owners can subscribe to email or SMS alerts when recovery is initiated, including requests submitted directly on-chain or through the service. The service monitors Social Recovery Module activity to detect these requests. + +See the [Recovery UX API reference](/wallet/recovery/ux-api/) for endpoints and request formats. ## Email/SMS Recovery API -A secure and user-friendly solution that uses email and phone verification for account recovery. Use it as a standalone recovery method or combine it with other guardians (hardware wallets, trusted contacts) to create a customized recovery threshold. Features include: +Use this API to add a service-managed guardian that signs recovery requests after the user verifies their email address or phone number. -### Email Recovery -Supports SMTP and OAuth2-based protocols. To verify email ownership, a confirmation code is sent to the user's email address. The user must enter this code to enable the guardian service. +1. **Set up recovery:** While the user still has access to the account, register their email address or phone number, verify it with a one-time code, and add the returned guardian address to the Social Recovery Module. +2. **Authorize a recovery:** When the user needs to recover the account, verify a new one-time code to obtain the service-managed guardian's signature for the recovery request. -### SMS Recovery -Supports SMS OTP verification. To verify phone number ownership, a confirmation code is sent to the user. The user must enter this code to enable the guardian service. +Email/SMS can be the sole recovery method or one guardian in a larger setup with trusted contacts or hardware wallets. The account's guardian threshold still determines how many approvals are needed. -### Multi-Factor Authentication -Supports MFA across multiple channels, including combinations of email and SMS. +The service also supports multi-factor authentication across channels, such as email and SMS. Contact us to request additional channels such as WhatsApp or Telegram. -### Custom Channels -Supports additional channels including WhatsApp and Telegram. Contact us to request support for your preferred channel. +See the [Email/SMS Recovery API reference](/wallet/recovery/auth-api/) for registration and recovery endpoints. ## How it works +Once guardians are configured, a successful recovery follows these steps: + +1. **Create a request.** A guardian submits a signed recovery request specifying the proposed new owners and their signing threshold. The service stores the request and alerts subscribed account owners. +2. **Collect approvals.** Guardians verify the request and submit signatures until the guardian threshold is met. +3. **Execute the request.** The approved request is submitted on-chain, starting the module's grace period. +4. **Wait through the grace period.** The current owner can cancel an unauthorized recovery if they still control the account. +5. **Finalize recovery.** After the grace period, finalization replaces the account's owners and applies the new signing threshold. + ```mermaid flowchart TD - A[Start] --> B[Listen for recovery requests from guardians] - B --> C{Recovery request received?} - C -->|No| B - C -->|Yes| D[Store recovery request] - D --> E[Return request for guardians to sign] - E --> F[Collect and store guardian signatures] - F --> G{All signatures collected?} - G -->|No| F - G -->|Yes| H[Execute recovery request] - H --> I[Wait for grace period to end] - I --> J{Grace period ended?} - J -->|No| I - J -->|Yes| K[Finalize recovery request] - K --> L[End] - - R{Check alert subscription} - R -->|Email| S[Send email alert] - R -->|SMS| T[Send SMS alert] - - C -->|Yes| R + A[Create recovery request] --> B[Collect guardian signatures] + B -->|Guardian threshold met| C[Execute on-chain] + C --> D[Grace period] + D -->|Period ends without cancellation| E[Finalize ownership change] + D -->|Current owner cancels| F[Recovery cancelled] ``` -## Reference Links - -- [Recovery explainer](/blog/making-accounts-recoverable) -- [Module SDK](/wallet/plugins/recovery-with-guardians) -- [Module contracts and audits](https://github.com/candidelabs/candide-contracts) -- [Source code](https://github.com/candidelabs/safe-recovery-service) -- [Recovery Service SDK](https://github.com/candidelabs/safe-recovery-service-sdk) +Execution and finalization are automatic when enabled in the service configuration. **New owners gain control only after finalization.** ## Recovery Request States -A recovery request goes through the following states: - -```mermaid -stateDiagram-v2 - [*] --> PENDING - PENDING --> EXECUTION_IN_PROGRESS: All signatures collected - EXECUTION_IN_PROGRESS --> EXECUTED: Transaction confirmed - EXECUTED --> FINALIZATION_IN_PROGRESS: Grace period ended - FINALIZATION_IN_PROGRESS --> FINALIZED: Finalization confirmed - FINALIZED --> [*] - PENDING --> EXECUTED: Auto-execute enabled -``` +The API's `status` field tracks progress through a successful recovery: | State | Description | |-------|-------------| -| `PENDING` | Recovery request created, waiting for guardian signatures | -| `EXECUTION-IN-PROGRESS` | All signatures collected, executing on-chain | -| `EXECUTED` | Recovery confirmed, new owners set. Waiting for grace period | -| `FINALIZATION-IN-PROGRESS` | Grace period ended, finalizing on-chain | -| `FINALIZED` | Recovery complete, new owners take control | +| `PENDING` | The request is stored and has not yet been executed. Guardian signatures can be collected. | +| `EXECUTION-IN-PROGRESS` | The guardian threshold is met and the execution transaction is being processed. | +| `EXECUTED` | The request has been executed on-chain. The grace period must end before finalization. Ownership has not changed. | +| `FINALIZATION-IN-PROGRESS` | The grace period has ended and the finalization transaction is being processed. | +| `FINALIZED` | Recovery is complete. The new owners and signing threshold are in effect. | + +## Get Started + +[Request service access](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5), then follow the guide for your recovery method: + +- **Personal guardians:** [Add a guardian](/wallet/plugins/how-to-add-a-guardian/), then follow the [Recovery Flow Guide](/wallet/plugins/recovery-flow-guide/). +- **Email/SMS:** [Enable email/SMS recovery](/wallet/plugins/add-candide-guardian/), then follow the [Email/SMS Recovery Flow](/wallet/plugins/recover-account-candide-guardian/). +- **Notifications:** Set up subscriptions with the [Recovery Alerts Guide](/wallet/plugins/recovery-alerts-guide/). +- **TypeScript integration:** Use the [Recovery Service SDK reference](/wallet/plugins/recovery-service-sdk-reference/). + +## Reference Links + +- [Recovery explainer](/blog/making-accounts-recoverable) +- [Social Recovery Module SDK reference](/wallet/plugins/recovery-module-reference/) +- [Module contracts and audits](https://github.com/candidelabs/candide-contracts) +- [Recovery Service source code](https://github.com/candidelabs/safe-recovery-service) +- [Recovery Service SDK source code](https://github.com/candidelabs/safe-recovery-service-sdk) diff --git a/docs/wallet/recovery/2-ux-api.mdx b/docs/wallet/recovery/2-ux-api.mdx index 6ab55b9..976e0bb 100644 --- a/docs/wallet/recovery/2-ux-api.mdx +++ b/docs/wallet/recovery/2-ux-api.mdx @@ -1,5 +1,5 @@ --- -title: Safe Recovery Service for UX Features and Alerts | API Reference +title: Safe Recovery UX API Reference description: API specification for Candide's Safe Recovery Service. Features signature aggregation, gas sponsorship, recovery request monitoring, signature storage, and auto-execution. keywords: [safe recovery, social recovery, smart wallet, email recovery, guardian recovery] --- @@ -23,75 +23,70 @@ import { postRecoveriesFinalizeByIdResponse, postRecoveriesExecuteById, postRecoveriesExecuteByIdResponse, - postAlertSubscribe, - postAlertSubscribeResponse, } from "/src/data/safeRecoveryService"; # Safe Recovery UX API -## Benefits +Use the Recovery UX API to collect guardian signatures, submit recovery transactions, track requests, and manage email/SMS alerts for Safe accounts. It works with the [Social Recovery Module](/wallet/plugins/recovery-with-guardians/). -| Feature | Description | -|----------------------------------|-----------------------------------------------------------------------------------------------------------------------| -| Alerts and Notifications | Account owners subscribe to receive notifications via email or SMS when a recovery request is initiated on-chain. | -| Guardians Sign Once | Off-chain signature collection eliminating the need for guardians to share links with one another. | -| Privacy Guaranteed | Guardians sign only off-chain and do not need to maintain a balance in their accounts, allowing them to preserve their pseudonymity with fresh accounts. | -| Social Engineering Protection | A communication system using emojis that allows guardians to verify and approve legitimate recovery requests from their rightful owners. | -| Auto Finalization After Grace Period | A built-in relayer automatically submits signed transactions on behalf of guardians for confirmation and finalization once the grace period has elapsed. | +For email/SMS verification that produces a guardian signature, use the [Email/SMS Recovery API](/wallet/recovery/auth-api/). For an explanation of the full recovery process, see the [service overview](/wallet/recovery/overview/). -:::info -To get started, request access [here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). -::: +## Before You Start + +- Enable the Social Recovery Module and configure guardians and a recovery threshold on the Safe account. +- [Request service access](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5) to obtain your service URL and Bearer token. +- Use the [Recovery Flow Guide](/wallet/plugins/recovery-flow-guide/) for a TypeScript integration example. ## Authentication -All API requests require a Bearer token in the Authorization header: +Include `Authorization: Bearer YOUR_BEARER_TOKEN` in every request. Replace `https://yourcompany.recovery.candide.dev` with your service URL. -```bash -curl -X POST \ - https://yourcompany.recovery.candide.dev/recoveries/create \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ - -d '{...}' -``` +Requests that create or approve a recovery also require a **guardian signature** over the recovery data. Alert subscription management requires an **owner-signed SIWE message**. These signatures authorize the account action; the Bearer token authenticates access to the API. + +Examples use placeholder addresses, IDs, messages, and signatures. Replace them with values from your integration. Expressions such as `siwe(...)` and `sign(message)` describe values to generate in your application; they are not literal API inputs. + +## Recovery Workflow -**/recoveries** -- **/recoveries/create** - - **POST**: Creates a new recovery request -- **/recoveries/fetchByAddress** - - **GET**: Fetches recovery requests -- **/recoveries/listByAddress** - - **GET**: Lists all recovery requests with filtering and pagination -- **/recoveries/fetchById** - - **GET**: Fetch a recovery request by ID -- **/recoveries/sign** - - **POST**: Collects a guardian signature -- **/recoveries/execute** - - **POST**: Execute a recovery request by ID -- **/recoveries/finalize** - - **POST**: Finalize a recovery request by ID - - -**/alerts** -- **/alerts/subscribe** - - **POST**: Creates an inactive alerts subscription for an account -- **/alerts/activate** - - **POST**: Activate subscription to recovery requests -- **/alerts/subscriptions** - - **GET**: Fetches active alerts subscriptions for an account. -- **/alerts/unsubscribe** - - **POST**: Unsubscribes from an active alerts subscription. +1. **Create:** Submit a request with the first guardian's signature using `/recoveries/create`. +2. **Approve:** Other guardians check the request's emoji sequence with the account owner through a trusted channel, then submit signatures using `/recoveries/sign`. +3. **Execute:** Once the guardian threshold is met, execution starts the on-chain grace period. +4. **Finalize:** After the grace period, finalization applies the new owners and signing threshold. The current owner can cancel the recovery during the grace period. + +The service can automate and sponsor execution and finalization when configured. Guardians can submit signatures off-chain without holding funds. Use the lookup endpoints to monitor progress; a successful submission does not by itself mean ownership has changed. See [request states](/wallet/recovery/overview/#recovery-request-states). + +`newThreshold` is the signing threshold for the **new Safe owners** after recovery. The existing **guardian threshold** determines how many guardians must approve the recovery. + +## Endpoints + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| POST | `/recoveries/create` | Create a request with a guardian signature. | +| POST | `/recoveries/sign` | Add another guardian's signature. | +| POST | `/recoveries/execute` | Submit execution once the guardian threshold is met. | +| POST | `/recoveries/finalize` | Submit finalization after the grace period. | +| GET | `/recoveries/fetchById` | Fetch one request by its service ID. | +| GET | `/recoveries/fetchByAddress` | Fetch requests for an account, chain, and recovery nonce. | +| GET | `/recoveries/listByAddress` | List requests with filters, sorting, and pagination. | +| POST | `/alerts/subscribe` | Create an inactive alert subscription and send a verification code. | +| POST | `/alerts/activate` | Verify the code and activate the subscription. | +| GET | `/alerts/subscriptions` | List active subscriptions for an account and owner. | +| POST | `/alerts/unsubscribe` | Remove a subscription. | ## Recoveries + ### Create Recovery Request -Creates a new recovery request by a guardian with a lost signer of a Safe account. Can only be initiated by guardians of the account. + +Create a request proposing new owners and their signing threshold. The `signer` must be a guardian of the Safe account, and `signature` must approve the recovery data. The service stores this first signature with the request. + #### `POST /recoveries/create` + -```json +```bash curl -X POST \ - https://yourcompany.recovery.candide.dev/create \ + https://yourcompany.recovery.candide.dev/recoveries/create \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", @@ -108,29 +103,34 @@ curl -X POST \ ```json { - "id": "123456789", - "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", - "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", - "chainId": 1, - "newOwners": [ - "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", - "0xB97A1C3993A551f0Febf030539630ACb77E6832D" - ], - "newThreshold": 2, - "nonce": "1234567890", - "signatures": [], - "executeData": { - "sponsored": true, - "transactionHash": "" - }, - "finalizeData": { - "sponsored": true, - "transactionHash": "" - }, - "status": "PENDING", - "discoverable": true, - "createdAt": "2023-04-18T12:34:56.789Z", - "updatedAt": "2023-04-18T12:34:56.789Z" + "id": "123456789", + "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", + "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", + "chainId": 1, + "newOwners": [ + "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", + "0xB97A1C3993A551f0Febf030539630ACb77E6832D" + ], + "newThreshold": 2, + "nonce": "0x1", + "signatures": [ + [ + "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ] + ], + "executeData": { + "sponsored": false, + "transactionHash": "" + }, + "finalizeData": { + "sponsored": false, + "transactionHash": "" + }, + "status": "PENDING", + "discoverable": true, + "createdAt": "2023-04-18T12:34:56.789Z", + "updatedAt": "2023-04-18T12:34:56.789Z" } ``` @@ -143,158 +143,99 @@ curl -X POST \ -### Fetch recovery by address -Fetches a recovery request by Safe account address and nonce. Requires `account`, `chainId`, and `nonce` parameters. -#### `GET /recoveries/fetchByAddress` +### Collect a guardian signature + +Add a guardian signature to an existing recovery request. The signature must approve the same recovery data as the request. + +#### `POST /recoveries/sign` -```json -curl -G "https://yourcompany.recovery.candide.dev/fetchByAddress" \ - --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ - --data-urlencode "chainId=11155111" \ - --data-urlencode "nonce=0x1" +```bash +curl -X POST \ + https://yourcompany.recovery.candide.dev/recoveries/sign \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": "123456789", + "signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }' ``` + ```json -[ - { - "id": "123456789", - "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", - "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", - "chainId": 1, - "newOwners": [ - "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", - "0xB97A1C3993A551f0Febf030539630ACb77E6832D" - ], - "newThreshold": 2, - "nonce": "1234567890", - "signatures": [], - "executeData": { - "sponsored": true, - "transactionHash": "" - }, - "finalizeData": { - "sponsored": true, - "transactionHash": "" - }, - "status": "PENDING", - "discoverable": true, - "createdAt": "2023-04-18T12:34:56.789Z", - "updatedAt": "2023-04-18T12:34:56.789Z" - }, - { - "id": "72682373", - "emoji": "πŸ’³πŸ“ΏπŸͺ£πŸ“₯πŸ“Ή", - "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", - "chainId": 1, - "newOwners": [ - "0x73f7b1184B5cD361cC0f7654998953E2a251dd58", - "0x7Cb027917b27BCb5963C548657a008BF45b25BDc" - ], - "newThreshold": 2, - "nonce": "1234567890", - "signatures": [], - "executeData": { - "sponsored": true, - "transactionHash": "" - }, - "finalizeData": { - "sponsored": true, - "transactionHash": "" - }, - "status": "PENDING", - "discoverable": true, - "createdAt": "2023-04-18T12:34:56.789Z", - "updatedAt": "2023-04-18T12:34:56.789Z" - } -] +{ + "success": true +} ``` - + - + -### List all recoveries by address -Lists all recovery requests for a Safe account with advanced filtering, pagination, and ordering. The `executed` and `finalized` filters are cross-checked with indexed on-chain data for accuracy. -#### `GET /recoveries/listByAddress` +### Execute a recovery by ID + +Submit the execution transaction for a request that has met the guardian threshold. Execution starts the grace period; it does not change ownership. + +#### `POST /recoveries/execute` -```json -curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \ - --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ - --data-urlencode "chainId=11155111" \ +```bash +curl -X POST \ + https://yourcompany.recovery.candide.dev/recoveries/execute \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": "123456789" + }' ``` + ```json { - "recoveries": [ - { - "id": "123456789", - "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", - "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", - "chainId": 1, - "newOwners": [ - "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", - "0xB97A1C3993A551f0Febf030539630ACb77E6832D" - ], - "newThreshold": 2, - "nonce": "1234567890", - "signatures": [], - "executeData": { - "sponsored": true, - "transactionHash": "" - }, - "finalizeData": { - "sponsored": true, - "transactionHash": "" - }, - "status": "PENDING", - "discoverable": true, - "createdAt": "2023-04-18T12:34:56.789Z", - "updatedAt": "2023-04-18T12:34:56.789Z" - } - ], - "total": 1 + "success": true } ``` - - + + - + -:::tip When to use fetchByAddress vs listByAddress -- **`/recoveries/fetchByAddress`**: Use when you know the exact nonce. Ideal for checking a specific pending request or looking up the current recovery state. -- **`/recoveries/listByAddress`**: Use when you need to query multiple requests with filtering, pagination, or sorting. Better for dashboard UIs showing recovery history. -::: +### Finalize recovery by ID -### Fetch recovery by ID -Fetch a recovery request by ID -#### `GET /recoveries/fetchById` +Submit the finalization transaction after the grace period has ended. Once finalized on-chain, the new owners and signing threshold take effect. + +#### `POST /recoveries/finalize` -```json -curl -G "https://yourcompany.recovery.candide.dev/fetchById" \ - --data-urlencode "id=0x123" +```bash +curl -X POST \ + https://yourcompany.recovery.candide.dev/recoveries/finalize \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + -H 'Content-Type: application/json' \ + -d '{ + "id": "123456789" + }' ``` @@ -302,123 +243,194 @@ curl -G "https://yourcompany.recovery.candide.dev/fetchById" \ ```json { - "id": "123456789", - "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", - "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", - "chainId": 1, - "newOwners": [ - "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", - "0xB97A1C3993A551f0Febf030539630ACb77E6832D" - ], - "newThreshold": 2, - "nonce": "1234567890", - "signatures": [], - "executeData": { - "sponsored": true, - "transactionHash": "", - }, - "finalizeData": { - "sponsored": true, - "transactionHash": "", - }, - "status": "PENDING", - "discoverable": true, - "createdAt": "2023-04-18T12:34:56.789Z", - "updatedAt": "2023-04-18T12:34:56.789Z" + "success": true } ``` - + - - + + -### Collect a guardian signature -Collects a guardian signature to store for later confirmation and finalization -#### `POST /recoveries/sign` +### Fetch recovery by address + +Fetch all discoverable requests matching a Safe account, chain, and recovery module nonce. The response is an array, which can be empty. Supply `nonce` as a hexadecimal string. + +#### `GET /recoveries/fetchByAddress` + -```json -curl -X POST \ - https://yourcompany.recovery.candide.dev/sign \ - -H 'Content-Type: application/json' \ - -d '{ - "id": "123456789", - "signer": "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", - "signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - }' +```bash +curl -G "https://yourcompany.recovery.candide.dev/recoveries/fetchByAddress" \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ + --data-urlencode "chainId=1" \ + --data-urlencode "nonce=0x1" ``` - ```json -{ - "success": "true" -} +[ + { + "id": "123456789", + "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", + "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", + "chainId": 1, + "newOwners": [ + "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", + "0xB97A1C3993A551f0Febf030539630ACb77E6832D" + ], + "newThreshold": 2, + "nonce": "0x1", + "signatures": [ + [ + "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ] + ], + "executeData": { + "sponsored": false, + "transactionHash": "" + }, + "finalizeData": { + "sponsored": false, + "transactionHash": "" + }, + "status": "PENDING", + "discoverable": true, + "createdAt": "2023-04-18T12:34:56.789Z", + "updatedAt": "2023-04-18T12:34:56.789Z" + }, + { + "id": "72682373", + "emoji": "πŸ’³πŸ“ΏπŸͺ£πŸ“₯πŸ“Ή", + "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", + "chainId": 1, + "newOwners": [ + "0x73f7b1184B5cD361cC0f7654998953E2a251dd58", + "0x7Cb027917b27BCb5963C548657a008BF45b25BDc" + ], + "newThreshold": 2, + "nonce": "0x1", + "signatures": [ + [ + "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ] + ], + "executeData": { + "sponsored": false, + "transactionHash": "" + }, + "finalizeData": { + "sponsored": false, + "transactionHash": "" + }, + "status": "PENDING", + "discoverable": true, + "createdAt": "2023-04-18T12:34:56.789Z", + "updatedAt": "2023-04-18T12:34:56.789Z" + } +] ``` - - + + - + -### Execute a recovery by ID -#### `POST /recoveries/execute` -Execute a recovery request by ID +### List all recoveries by address + +Lists all recovery requests for a Safe account with advanced filtering, pagination, and ordering. The `executed` and `finalized` filters are cross-checked with indexed on-chain data for accuracy. + +#### `GET /recoveries/listByAddress` -```json -curl -X POST \ - https://yourcompany.recovery.candide.dev/execute \ - -H 'Content-Type: application/json' \ - -d '{ - "id": 123456789 - }' +```bash +curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ + --data-urlencode "chainId=1" ``` - ```json { - "success": "true" + "recoveries": [ + { + "id": "123456789", + "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", + "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", + "chainId": 1, + "newOwners": [ + "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", + "0xB97A1C3993A551f0Febf030539630ACb77E6832D" + ], + "newThreshold": 2, + "nonce": "0x1", + "signatures": [ + [ + "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ] + ], + "executeData": { + "sponsored": false, + "transactionHash": "" + }, + "finalizeData": { + "sponsored": false, + "transactionHash": "" + }, + "status": "PENDING", + "discoverable": true, + "createdAt": "2023-04-18T12:34:56.789Z", + "updatedAt": "2023-04-18T12:34:56.789Z" + } + ], + "total": 1 } ``` - - + + - + -### Finalize recovery by ID -#### `POST /recoveries/finalize` -Finalize a recovery request by ID +:::tip When to use fetchByAddress vs listByAddress +- **`/recoveries/fetchByAddress`**: Use when you know the exact nonce. Ideal for checking a specific pending request or looking up the current recovery state. +- **`/recoveries/listByAddress`**: Use when you need to query multiple requests with filtering, pagination, or sorting. Better for dashboard UIs showing recovery history. +::: + +### Fetch recovery by ID + +Fetch one request using the `id` returned when it was created. + +#### `GET /recoveries/fetchById` -```json -curl -X POST \ - https://yourcompany.recovery.candide.dev/finalize \ - -H 'Content-Type: application/json' \ - -d '{ - "id": 123456789 - }' +```bash +curl -G "https://yourcompany.recovery.candide.dev/recoveries/fetchById" \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + --data-urlencode "id=123456789" ``` @@ -426,36 +438,74 @@ curl -X POST \ ```json { - "success": "true" + "id": "123456789", + "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", + "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", + "chainId": 1, + "newOwners": [ + "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", + "0xB97A1C3993A551f0Febf030539630ACb77E6832D" + ], + "newThreshold": 2, + "nonce": "0x1", + "signatures": [ + [ + "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ] + ], + "executeData": { + "sponsored": false, + "transactionHash": "" + }, + "finalizeData": { + "sponsored": false, + "transactionHash": "" + }, + "status": "PENDING", + "discoverable": true, + "createdAt": "2023-04-18T12:34:56.789Z", + "updatedAt": "2023-04-18T12:34:56.789Z" } ``` - - + + - - + + ## Alerts -### Alerts: Subscribe to recovery requests + +Alert subscriptions are separate from email/SMS recovery registrations. Subscribing to alerts does not add a guardian or enable email/SMS recovery. + +Create a subscription, then activate it with the code sent to the selected channel. Subscriptions monitor the Safe account across supported chains with alerts enabled. The `chainId` identifies the network used to verify the signing owner; subscription creation also checks that this owner belongs to the Safe on that chain. + +The `owner` signs the SIWE message for subscription creation, lookup, and removal. See [SIWE signing](/wallet/recovery/auth-api/#how-to-sign-messages-siwe-eip-4361) and the [Recovery Alerts Guide](/wallet/plugins/recovery-alerts-guide/). + +### Create an Alert Subscription {#alerts-subscribe-to-recovery-requests} + #### `POST /alerts/subscribe` -Creates an inactive alerts subscription for an account, both onchain and offchain. It then needs to be activated through challenge submission using `POST /alerts/activate`. + +Create an inactive subscription and send a verification code to the target email address or phone number. Save the returned `subscriptionId` and activate it using `/alerts/activate`. -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/alerts/subscribe \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "account": "0x...", + "owner": "0x...", "chainId": 1, "channel": "email", "target": "user@example.com", - "message": "siwe(chainId, statement(channel, target))", + "message": "siwe(chainId, statement(account, channel, target))", "signature": "sign(message)" }' ``` @@ -465,36 +515,41 @@ curl -X POST \ ```json { - "subscriptionId": "unique-subscription-id", // please note that this alerts subscription needs to be activated using the next endpoint + "subscriptionId": "unique-subscription-id" } ``` -- `account`: The smart account address requesting registration. -- `chainId`: The chain id in which the account resides (this is used to verify the signature field only, the alert will trigger for any action for this account across any chain) +- `account`: The Safe account to monitor. +- `owner`: The Safe owner authorizing the subscription and signing the SIWE message. +- `chainId`: The supported chain used to verify the owner and signature. Subscriptions cover all supported chains with alerts enabled. - `channel`: Either `"email"` or `"sms"` (defines the delivery channel). -- `target`: The email or phone number for authentication. -- `message`: SIWE (EIP-4361) message statement. Statement: +- `target`: The email address or phone number that receives alerts. +- `signature`: The signature from `owner` over the complete SIWE message. +- `message`: The complete SIWE message containing this exact statement: ``` -I agree to receive Social Recovery Module alert notifications for my account address on all supported chains sent to {{target}} +I agree to receive Social Recovery Module alert notifications for {{account}} on all supported chains sent to {{target}} (via {{channel}}) ``` -- `signature`: signature proving the request is initiated from the account +Replace `{{account}}` with the lowercase Safe address, `{{target}}` with the destination, and `{{channel}}` with `email` or `sms`. + +See [SIWE signing](/wallet/recovery/auth-api/#how-to-sign-messages-siwe-eip-4361) to construct the message and signature. -See [example guide](/wallet/recovery/auth-api/#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) +### Activate an Alert Subscription {#alerts-activate-subscription-to-recovery-requests} -### Alerts: Activate subscription to recovery requests #### `POST /alerts/activate` -Verifies submitted challenge and activates alerts subscription. + +Submit the verification code sent during subscription creation. Alerts begin after activation. -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/alerts/activate \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "subscriptionId": "unique-subscription-id", @@ -507,7 +562,7 @@ curl -X POST \ ```json { - "success": true, + "success": true } ``` @@ -516,18 +571,22 @@ curl -X POST \ - `subscriptionId`: The unique ID received in the subscription response. - `challenge`: The code received via email/SMS. -### Alerts: Get active subscription -Fetches active alerts subscriptions for an account. +### List Active Alert Subscriptions {#alerts-get-active-subscription} + +Fetch active subscriptions for the specified Safe account and signing owner. #### `GET /alerts/subscriptions` + -```json +```bash curl -G "https://yourcompany.recovery.candide.dev/alerts/subscriptions" \ - --data-urlencode "account=0x...", - --data-urlencode "chainId=0x1", - --data-urlencode "message=siwe(chainId, statement)", + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + --data-urlencode "account=0x..." \ + --data-urlencode "owner=0x..." \ + --data-urlencode "chainId=1" \ + --data-urlencode "message=siwe(chainId, statement)" \ --data-urlencode "signature=sign(message)" ``` @@ -548,31 +607,38 @@ curl -G "https://yourcompany.recovery.candide.dev/alerts/subscriptions" \ -- `account`: The smart account address. -- `chainId`: The chain id in which the account resides (this is used to verify the signature field only, the alerts are global for this account accross all supported chains that have alerts enabled) -- `message`: SIWE (EIP-4361) message statement. Statement: +- `account`: The Safe account being monitored. +- `owner`: The owner whose subscriptions to retrieve and who signs the SIWE message. +- `chainId`: The supported chain used to verify the owner and signature. Subscriptions cover all supported chains with alerts enabled. +- `signature`: The signature from `owner` over the complete SIWE message. +- `message`: The complete SIWE message containing this exact statement: ``` I request to retrieve all Social Recovery Module alert subscriptions linked to my account ``` -- `signature`: signature proving the request is initiated from the account +See [SIWE signing](/wallet/recovery/auth-api/#how-to-sign-messages-siwe-eip-4361) to construct the message and signature. -See [example guide](/wallet/recovery/auth-api/#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) +### Remove an Alert Subscription {#alerts-unsubscribe} -### Alerts: Unsubscribe -Unsubscribes from an active alerts subscription. +Remove the subscription identified by `subscriptionId`. Include the owner's SIWE message and signature. #### `POST /alerts/unsubscribe` + -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/alerts/unsubscribe \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ - "subscriptionId": "unique-subscription-id" + "subscriptionId": "unique-subscription-id", + "owner": "0x...", + "chainId": 1, + "message": "siwe(chainId, statement)", + "signature": "sign(message)" }' ``` @@ -587,19 +653,27 @@ curl -X POST \ -- `subscriptionId`: The unique ID received in the subscription response. +- `subscriptionId`: The subscription to remove. +- `owner`: The owner signing the request. +- `chainId`: The supported chain used to verify the signature. +- `message`: The complete SIWE message containing the statement below. +- `signature`: The signature from `owner` over that message. + +```text +I request to unsubscribe from all Social Recovery Module alert subscriptions linked to my account +``` + +Use this exact statement even when removing one subscription. The `subscriptionId` selects the subscription to remove. ## Error Handling -The API uses standard HTTP status codes to indicate the success or failure of a request. Error responses include a JSON object with the following structure: +Use the HTTP status to determine whether a request succeeded. Service errors contain top-level `code` and `message` fields. For example: ```json { - "error": { - "code": 404, - "message": "Recovery request not found" - } + "code": 404, + "message": "Recovery request not found" } ``` @@ -610,16 +684,15 @@ The API uses standard HTTP status codes to indicate the success or failure of a | 200 | Success | | 400 | Bad Request - Invalid parameters or missing required fields | | 401 | Unauthorized - Invalid or missing Bearer token | +| 403 | Forbidden - Signature verification failed, challenge invalid or expired, or action not permitted | | 404 | Not Found - Resource not found | +| 409 | Conflict - The alert subscription already exists | | 429 | Too Many Requests - Rate limit exceeded | | 500 | Internal Server Error - Something went wrong on the server | -### Common Error Messages +### Troubleshooting -| Message | Description | -|---------|-------------| -| Recovery request not found | The requested recovery ID does not exist | -| Invalid signature | The signature verification failed | -| Guardian not found | The signer is not a registered guardian | -| Insufficient signatures | Not enough guardian signatures collected | -| Rate limit exceeded | Too many requests, please try again later | +- **Invalid signature:** Check the signing address, chain ID, and exact message or recovery data. SIWE messages must use the statement for the requested action. +- **Invalid or expired code:** Verify that the code matches the challenge ID. If it has expired, create a new alert subscription and verify its new code. +- **Missing resource:** Use the ID returned by the corresponding creation endpoint. +- **Rate limit:** Wait before retrying the request. diff --git a/docs/wallet/recovery/3-auth-api.mdx b/docs/wallet/recovery/3-auth-api.mdx index 9425fa7..f2e1110 100644 --- a/docs/wallet/recovery/3-auth-api.mdx +++ b/docs/wallet/recovery/3-auth-api.mdx @@ -1,6 +1,6 @@ --- -title: Safe Recovery Service using Email/SMS | API Reference -description: API specification for Candide's Safe Recovery Service. Features email/SMS recovery, multifactor auth, and an alert system for on-chain and off-chain monitoring for active recovery. +title: Email/SMS Recovery API Reference +description: Register email/SMS recovery methods and obtain guardian signatures with Candide's Safe Recovery Service API. keywords: [safe recovery, social recovery, smart wallet, email-sms recovery, guardian recovery] --- @@ -9,20 +9,6 @@ import TabItem from "@theme/TabItem"; import { DataTable } from "/src/components/Table"; import { - postRecoveriesCreate, - postRecoveriesCreateResponse, - getRecoveriesFetchByAddress, - getRecoveriesFetchByAddressResponse, - getRecoveriesFetchById, - getRecoveriesFetchByIdResponse, - postRecoveriesSign, - postRecoveriesSignResponse, - postRecoveriesFinalizeById, - postRecoveriesFinalizeByIdResponse, - postRecoveriesExecuteById, - postRecoveriesExecuteByIdResponse, - postAlertSubscribe, - postAlertSubscribeResponse, postAuthRegister, postAuthRegisterResponse, postAuthSubmit, @@ -39,56 +25,74 @@ import { # Authentication-Based Recovery API (Email / SMS) -A Safe guardian service that uses email and phone verification to facilitate account recovery. It can be used as a default recovery method or combined -with other guardians (such as hardware wallets or trusted contacts) to create a customized recovery threshold. +Use this API to register an email address or phone number as a recovery method, then obtain a service-managed guardian's signature after the user verifies a one-time password (OTP). -:::info -To get started, request access [here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). -::: +The signature counts as one guardian approval under the account's recovery threshold. Submit it through the [Recovery UX API](/wallet/recovery/ux-api/) to continue recovery. + +## Before You Start + +- Deploy the Safe account and enable the [Social Recovery Module](/wallet/plugins/recovery-with-guardians/). +- [Request service access](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5) to obtain your service URL and Bearer token. +- Register the recovery method while the user still controls the account. Registrations are specific to an account and chain. + +For a TypeScript walkthrough, see [Enable Email/SMS Recovery](/wallet/plugins/add-candide-guardian/) and the [Email/SMS Recovery Flow](/wallet/plugins/recover-account-candide-guardian/). ## Authentication -All API requests require a Bearer token in the Authorization header: +Include `Authorization: Bearer YOUR_BEARER_TOKEN` in every request. Replace `https://yourcompany.recovery.candide.dev` with your service URL. -```bash -curl -X POST \ - https://yourcompany.recovery.candide.dev/auth/register \ - -H 'Content-Type: application/json' \ - -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ - -d '{...}' -``` +Registration, registration lookup, and deletion also require a **SIWE message signed by the Safe account**. During recovery, the user verifies codes sent to registered channels to authorize the service-managed guardian's signature. See [SIWE signing](#how-to-sign-messages-siwe-eip-4361). -**/auth** (Registration) -- **/auth/register** - - **POST**: Submit a registration request -- **/auth/submit** - - **POST**: Submits the receive OTP -- **/auth/registrations** - - **GET**: Fetch active registration -- **/auth/delete** - - **POST**: Delete a registration +Examples use placeholder addresses, IDs, messages, and signatures. Replace them with values from your integration. Expressions such as `siwe(...)` and `sign(message)` describe values to generate in your application; they are not literal API inputs. -**/auth/signature** (Recovery) -- **/auth/signature/request** - - **POST**: Request to recover an account -- **/auth/signature/submit** - - **POST**: Confirm recovery with OTP challenge +## Integration Workflow +**Set up recovery while the user has account access:** +1. Call `/auth/register` with a Safe-signed SIWE message to send a verification code. +2. Submit the code to `/auth/submit` to receive a `registrationId` and `guardianAddress`. +3. Add that guardian address to the account's Social Recovery Module on-chain. Registration alone does not add the guardian. -## Auth Registration -### Email / SMS +**Recover the account after access is lost:** -Submit a registration request with the target smart account to protect using the choice of channel (email or SMS). -The user will then receive an OTP code to later submit the challenge in `/auth/submit`. +1. Call `/auth/signature/request` with the proposed new owners and their signing threshold. +2. Submit verification codes to `/auth/signature/submit` until the response includes `signer` and `signature`. +3. Use that signature to [create a recovery request](/wallet/recovery/ux-api/#create-recovery-request), or [add it to an existing request](/wallet/recovery/ux-api/#collect-a-guardian-signature) with the same recovery data. Collect any other required guardian approvals, execute recovery, and finalize after the grace period. + +There are three separate thresholds: + +| Value | What it controls | +|-------|------------------| +| `requiredVerifications` | How many channel verifications are needed to obtain the service-managed guardian's signature. | +| Guardian threshold | How many guardian approvals the Social Recovery Module requires to execute recovery. | +| `newThreshold` | How many of the new Safe owners must sign account transactions after recovery. | + +## Endpoints + +| Method | Endpoint | Purpose | +|--------|----------|---------| +| POST | `/auth/register` | Request registration and send a verification code. | +| POST | `/auth/submit` | Verify the code and return the guardian address. | +| GET | `/auth/registrations` | List the account's registered methods on a chain. | +| POST | `/auth/delete` | Delete a registered method. | +| POST | `/auth/signature/request` | Request verification codes for a proposed recovery. | +| POST | `/auth/signature/submit` | Verify a code and return a guardian signature once enough verifications are collected. | + +## Register and Manage Recovery Methods {#auth-registration} + +### Register an Email Address or Phone Number {#email--sms} + +Send a verification code to the selected email address or phone number. Save the returned `challengeId` for `/auth/submit`. #### `POST /auth/register` + -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/auth/register \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "account":"0x...", @@ -105,7 +109,7 @@ curl -X POST \ ```json { - "challengeId":"unique-challenge-id", + "challengeId": "unique-challenge-id" } ``` @@ -119,30 +123,31 @@ curl -X POST \ - `account`: The smart account address requesting registration. -- `chainId`: The chain id in which your account resides (for multi-chain wallets, users will need to register per chain) +- `chainId`: The chain where the Safe is deployed. Register separately for each chain. - `channel`: Either `"email"` or `"sms"` (defines the authentication type). - `target`: The email or phone number for authentication. -- `message`: SIWE (EIP-4361) message statement. Statement: +- `signature`: A signature valid for the Safe account over the complete SIWE message. +- `message`: The complete SIWE message containing this exact statement: ``` -I authorize Safe Recovery Service to sign a recovery request for my account after I authenticate using {{target}} via {{channel}} +I authorize Safe Recovery Service to sign a recovery request for my account after I authenticate using {{target}} (via {{channel}}) ``` -- `signature`: signature proving the request is initiated from the account +See [SIWE signing](#how-to-sign-messages-siwe-eip-4361) for the exact statement and signing requirements. -See [example guide](#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign-In with Ethereum (SIWE). +### Verify Registration {#submit-confirmation-using-otp} -### Submit Confirmation Using OTP -Submit the received OTP code to confirm ownership of the target channel. +Submit the code to confirm ownership of the email address or phone number. The response returns the guardian address to add to the Social Recovery Module on-chain. #### `POST /auth/submit` -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/auth/submit \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "challengeId":"unique-challenge-id", @@ -154,8 +159,8 @@ curl -X POST \ ```json { - "registrationId": "unique-registration-id", - "guardianAddress": "0x...", + "registrationId": "unique-registration-id", + "guardianAddress": "0x..." } ``` @@ -167,18 +172,21 @@ curl -X POST \ -### Get active registration -Fetch the registration of the protected smart account -#### `GET /auth/registrations` +### List Registered Recovery Methods {#get-active-registration} + +List the registered recovery methods for the specified Safe account and chain. The response contains an empty array if no methods are registered. + +#### `GET /auth/registrations` -```json +```bash curl -G "https://yourcompany.recovery.candide.dev/auth/registrations" \ - --data-urlencode "account=0x...", - --data-urlencode "chainId=0x1", - --data-urlencode "message=siwe(chainId, statement)", + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ + --data-urlencode "account=0x..." \ + --data-urlencode "chainId=1" \ + --data-urlencode "message=siwe(chainId, statement)" \ --data-urlencode "signature=sign(message)" ``` @@ -191,7 +199,7 @@ curl -G "https://yourcompany.recovery.candide.dev/auth/registrations" \ { "id": "unique-registration-id", "channel": "email", - "target": "user@example.com", + "target": "user@example.com" } ] } @@ -205,23 +213,26 @@ curl -G "https://yourcompany.recovery.candide.dev/auth/registrations" \ -See [example guide](#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) +See [SIWE signing](#how-to-sign-messages-siwe-eip-4361) for the exact statement and signing requirements. + +### Delete a Recovery Method {#delete} -### Delete -Deletes a registration +Delete the registration identified by `registrationId`, using a SIWE message signed by the Safe account. This removes the service registration; it does not revoke the guardian on-chain. #### `POST /auth/delete` + -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/auth/delete \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "registrationId":"unique-registration-id", "message": "siwe(chainId, statement(registrationId))", - "signature": "sign(registrationId, timestamp)" + "signature": "sign(message)" }' ``` @@ -230,7 +241,7 @@ curl -X POST \ ```json { - "success": "true" + "success": true } ``` @@ -242,19 +253,23 @@ curl -X POST \ -See [example guide](#how-to-sign-messages-siwe-eip-4361) how to construct the message and signature using Sign in With Ethereum (SIWE) +See [SIWE signing](#how-to-sign-messages-siwe-eip-4361) for the exact statement and signing requirements. + +## Obtain a Recovery Signature {#auth-recovery} + +### Request Recovery Verification {#request-to-recover-an-account} + +Propose new Safe owners and their signing threshold. The service sends verification codes to registered channels and returns a `requestId`, the required number of verifications, and the available challenges. -## Auth Recovery -### Request to recover an account -Request a signature from the service to recover an account given the new owners and threshold #### `POST /auth/signature/request` -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/auth/signature/request \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "account":"0x...", @@ -269,15 +284,15 @@ curl -X POST \ ```json { - "requestId":"unique-signature-request-id", - "requiredVerifications": 1, - "auths": [ - { - "challengeId": "unique-challenge-id", - "channel": "email", - "target": "us**@exa****.com" - } - ] + "requestId": "unique-signature-request-id", + "requiredVerifications": 1, + "auths": [ + { + "challengeId": "unique-challenge-id", + "channel": "email", + "target": "us**@exa****.com" + } + ] } ``` @@ -289,16 +304,19 @@ curl -X POST \ -### Confirm recovery with OTP challenge -Request to submits the signature with the provided OTP code challenge and id +### Verify a Code and Collect the Signature {#confirm-recovery-with-otp-challenge} + +Submit a code with its `challengeId` and the shared `requestId`. Each successful submission verifies one challenge. The response includes `signer` and `signature` only after `requiredVerifications` is met. + #### `POST /auth/signature/submit` -```json +```bash curl -X POST \ https://yourcompany.recovery.candide.dev/auth/signature/submit \ + -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ -H 'Content-Type: application/json' \ -d '{ "requestId": "unique-signature-request-id", @@ -312,9 +330,9 @@ curl -X POST \ ```json { - "success": true, - "signer": "0x...", - "signature": "0x..." + "success": true, + "signer": "0x...", + "signature": "0x..." } ``` @@ -326,95 +344,57 @@ curl -X POST \ -## How to Sign Messages (SIWE EIP-4361) +If more verifications are needed, a successful submission returns only: - - - -```typescript -import { SiweMessage } from "siwe"; -import { hexlify, randomBytes } from "ethers"; - -import { personalSign, getMessageHashForSafe } from "./safe-utils" - -function generateSIWEMessageSignaturePair(safeAccountAddress: string, statement: string, chainId: string): [string, string] { - const siweMessage = new SiweMessage({ - version: "1", - address: ethers.getAddress(accountAddress), - domain: "service://safe-recovery-safeAccountAddress", - uri: "service://safe-recovery-service", - statement, - chainId: Number(chainId), - nonce: hexlify(randomBytes(24)), - }); - const message = siweMessage.prepareMessage(); - const signature = personalSign(safeAccountAddress, message, BigInt(chainId)); - return [message, signature]; +```json +{ + "success": true } ``` - +Continue with another challenge from the same request until `signer` and `signature` are returned. A guardian signature authorizes recovery; it does not execute a transaction or change ownership by itself. - -```typescript -import { hashMessage, Wallet } from "ethers"; - -export function getMessageHashForSafe(safeAccountAddress: string, message: string, chainId: BigInt) { - const SAFE_MSG_TYPEHASH = "0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca"; - const DOMAIN_SEPARATOR_TYPEHASH = "0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218"; - const domainSeparator = ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode( - ["bytes32", "uint256", "address"], - [DOMAIN_SEPARATOR_TYPEHASH, chainId, safeAccountAddress] - )); - const encodedMessage = ethers.AbiCoder.defaultAbiCoder().encode( - ["bytes32", "bytes32"], - [SAFE_MSG_TYPEHASH, ethers.keccak256(message)] - ); - const messageHash = ethers.keccak256(ethers.solidityPacked( - ["bytes1", "bytes1", "bytes32", "bytes32",], - [Uint8Array.from([0x19]), Uint8Array.from([0x01]), domainSeparator, ethers.keccak256(encodedMessage)] - )); - return messageHash; -} +## How to Sign Messages (SIWE EIP-4361) -export function personalSign(safeAccountAddress: string, message: string, chainId: BigInt){ - const hash = hashMessage(message); - const safeMessageHash = await getMessageHashForSafe(safeAccountAddress, message, chainId); - const signer = new Wallet(process.env.privateKey) - return signer.signingKey.sign(messageHash).serialized; -} -``` - +SIWE (Sign-In with Ethereum) messages authorize registration and alert-management actions. Submit the complete prepared message in `message` and its corresponding signature in `signature`. - +### Choose the Signing Account -``` -service://safe-recovery-service wants you to sign in with your Ethereum account: -0x13D6D891307758afc45EE42C90bFE7636C32088b +| Endpoints | SIWE address and signature | +|-----------|----------------------------| +| `/auth/register`, `/auth/registrations`, `/auth/delete` | Use the **Safe account address** as the SIWE address. Produce a Safe-compatible signature that satisfies the account's owner threshold. | +| `/alerts/subscribe`, `/alerts/subscriptions`, `/alerts/unsubscribe` | Use the **`owner` address** as the SIWE address. Sign with that owner. Subscription creation checks that this address is an owner of the Safe. | -I request to retrieve all Social Recovery Module alert subscriptions linked to my account +For Safe accounts, follow the [registration signing example](/wallet/plugins/add-candide-guardian/#step-3-sign-and-submit-registration), which uses the Safe message-signing helpers and formats the owner signatures for verification. For alert subscriptions, follow the [Recovery Alerts Guide](/wallet/plugins/recovery-alerts-guide/). -URI: service://safe-recovery-service -Version: 1 -Chain ID: 11155420 -Nonce: 0x95e25544f0f05b90c12b92d5a0d29666b99c77a47b00e854 -Issued At: 2025-03-13T15:47:08.746Z -``` +### Use the Exact Statement - +The service checks the statement against the requested action. Preserve its wording and punctuation, and substitute the values in braces. - +| Action | Statement | +|--------|-----------| +| Register a recovery method | `I authorize Safe Recovery Service to sign a recovery request for my account after I authenticate using {{target}} (via {{channel}})` | +| List recovery methods | `I request to retrieve all authentication methods currently registered to my account with Safe Recovery Service` | +| Delete a recovery method | `I request to remove the authentication method with registration ID {{id}} from my account on Safe Recovery Service` | + +Use the registration ID for `{{id}}`, the email address or phone number for `{{target}}`, and `email` or `sms` for `{{channel}}`. The [alert endpoints](/wallet/recovery/ux-api/#alerts) list their own statements. + +### Prepare and Sign the Message + +1. Construct a SIWE message with the signing address above, the correct chain ID, the exact statement, a domain and URI, a fresh nonce, and an issued-at timestamp. +2. Prepare the message with a SIWE library or the [Recovery Service SDK](/wallet/plugins/recovery-service-sdk-reference/). +3. Sign that complete message using the appropriate signing account and submit both values unchanged. + +The statement alone is not the full SIWE message. The chain ID must match the request, or the registration's chain for deletion. Generate a fresh message when retrying an expired authorization. ## Error Handling -The API uses standard HTTP status codes to indicate the success or failure of a request. Error responses include a JSON object with the following structure: +Use the HTTP status to determine whether a request succeeded. Service errors contain top-level `code` and `message` fields. For example: ```json { - "error": { - "code": 404, - "message": "Registration not found" - } + "code": 404, + "message": "Could not find registration with this id" } ``` @@ -425,16 +405,15 @@ The API uses standard HTTP status codes to indicate the success or failure of a | 200 | Success | | 400 | Bad Request - Invalid parameters or missing required fields | | 401 | Unauthorized - Invalid or missing Bearer token | +| 403 | Forbidden - Signature verification failed, challenge invalid or expired, or action not permitted | | 404 | Not Found - Resource not found | +| 409 | Conflict - The recovery method is already registered | | 429 | Too Many Requests - Rate limit exceeded | | 500 | Internal Server Error - Something went wrong on the server | -### Common Error Messages +### Troubleshooting -| Message | Description | -|---------|-------------| -| Registration not found | The requested registration ID does not exist | -| Invalid signature | The SIWE signature verification failed | -| Challenge expired | The OTP challenge has expired | -| Invalid challenge | The OTP code provided is incorrect | -| Rate limit exceeded | Too many requests, please try again later | +- **Invalid signature:** Check the signing address, chain ID, and exact message or recovery data. SIWE messages must use the statement for the requested action. +- **Invalid or expired code:** Verify that the code matches the challenge ID. If it has expired, start a new registration or recovery verification request as appropriate. +- **Missing resource:** Use the ID returned by the corresponding creation endpoint. +- **Rate limit:** Wait before retrying the request. diff --git a/src/data/safeRecoveryService.ts b/src/data/safeRecoveryService.ts index 8ba1204..666c4f8 100644 --- a/src/data/safeRecoveryService.ts +++ b/src/data/safeRecoveryService.ts @@ -27,12 +27,12 @@ const recoveryRequestSchema = [ { key: "newThreshold", type: "number", - description: "The new threshold for the Safe account", + description: "The signing threshold for the new Safe owners after recovery; not the guardian approval threshold", }, { key: "nonce", - type: "bigint", - description: "Recovery module contract nonce", + type: "string", + description: "Recovery module nonce serialized as a hexadecimal string in JSON responses", }, { key: "signatures", @@ -52,7 +52,7 @@ const recoveryRequestSchema = [ description: "The transaction hash of the recovery execution", }, ], - description: "An object field representing the finalization recovery transaction", + description: "Execution transaction details", }, { key: "finalizeData", @@ -72,7 +72,7 @@ const recoveryRequestSchema = [ { key: "status", type: "string", - description: "The status of the recovery request: PENDING | EXECUTED | FINALIZED | FINALIZATION-IN-PROGRESS | FAILED" + description: "The status of the recovery request: PENDING | EXECUTION-IN-PROGRESS | EXECUTED | FINALIZATION-IN-PROGRESS | FINALIZED" }, { key: "discoverable", @@ -87,7 +87,7 @@ const recoveryRequestSchema = [ { key: "updatedAt", type: "datetime", - description: "The date and time of the recovery request that was created", + description: "The date and time the recovery request was last updated", }, ]; @@ -107,7 +107,7 @@ export const postRecoveriesCreate = [ { key: "newThreshold", type: "number", - description: "The new threshold to the Safe account", + description: "The signing threshold for the new Safe owners after recovery; not the guardian approval threshold", }, { key: "chainId", @@ -141,8 +141,8 @@ export const getRecoveriesFetchByAddress = [ }, { key: "nonce", - type: "number", - description: "Recovery module contract nonce", + type: "string", + description: "Recovery module contract nonce as a hexadecimal string, such as 0x1", }, ]; @@ -183,9 +183,9 @@ export const postRecoveriesSign = [ ]; export const postRecoveriesSignResponse = [{ - key: "status", - type: "true", - description: "true = signature is valid", + key: "success", + type: "boolean", + description: "True when the guardian signature has been accepted", }]; export const postRecoveriesExecuteById = [ @@ -199,8 +199,8 @@ export const postRecoveriesExecuteById = [ export const postRecoveriesExecuteByIdResponse = [ { key: "success", - type: "true", - description: "Return true once finilized. Else returns error", + type: "boolean", + description: "True when the execution request succeeds; use the recovery status to track on-chain progress", }, ]; @@ -216,8 +216,8 @@ export const postRecoveriesFinalizeById = [ export const postRecoveriesFinalizeByIdResponse = [ { key: "success", - type: "true", - description: "Return true once finilized. Else returns error", + type: "boolean", + description: "True when the finalization request succeeds; use the recovery status to confirm completion", }, ]; @@ -363,7 +363,7 @@ export const postAuthSubmitResponse = [ { key: "guardianAddress", type: "string", - description: "The guardian address added to the Safe account", + description: "The service-managed guardian address to add to the Social Recovery Module on-chain", }, ]; @@ -438,7 +438,7 @@ export const postAuthSignatureRequest = [ { key: "newThreshold", type: "number", - description: "The new threshold for the Safe account", + description: "The signing threshold for the new Safe owners after recovery; not the guardian approval threshold", }, { key: "chainId", From a7bbd0761a5203f2c3da5bf02879cbb4b2fa47f9 Mon Sep 17 00:00:00 2001 From: sednaoui Date: Tue, 22 Sep 2026 18:10:16 +0200 Subject: [PATCH 2/3] docs: fix Safe Recovery Service claims to match the service - listByAddress returns a plain array, not a {recoveries, total} envelope; document the real nonce, createdAt, limit, and offset query params - execution and finalization are gas-sponsored but client-triggered, never submitted automatically by the service - alerts fire on on-chain module events, not on off-chain request creation - guardian signature failures are 400; 403 is for SIWE and sponsorship checks - drop the stale postAlertSubscribe schemas and align finalizeData wording Co-Authored-By: Claude Fable 5.1 --- docs/wallet/plugins/4-recovery-flow-guide.mdx | 2 +- docs/wallet/recovery/1-overview.mdx | 16 ++-- docs/wallet/recovery/2-ux-api.mdx | 83 ++++++++++--------- src/data/safeRecoveryService.ts | 59 ++++++------- 4 files changed, 75 insertions(+), 85 deletions(-) diff --git a/docs/wallet/plugins/4-recovery-flow-guide.mdx b/docs/wallet/plugins/4-recovery-flow-guide.mdx index ee0165b..101bf2a 100644 --- a/docs/wallet/plugins/4-recovery-flow-guide.mdx +++ b/docs/wallet/plugins/4-recovery-flow-guide.mdx @@ -74,7 +74,7 @@ Initialize the recovery service and guardian references.
About the Recovery Service -The [Candide Recovery Service](/wallet/recovery/overview/) is an optional hosted service that streamlines recovery by automatically executing recovery after collecting required guardian signatures and finalizing it after the grace period expires. +The [Candide Recovery Service](/wallet/recovery/overview/) is an optional hosted service that streamlines recovery by collecting guardian signatures off-chain and sponsoring gas for the execution and finalization transactions your application submits. Request access to the recovery service [here](https://app.formbricks.com/s/brdzlw0t897cz3mxl3ausfb5). **Alternative**: Anyone can [execute](/blog/making-accounts-recoverable/#multi-confirm-recovery) and [finalize](/blog/making-accounts-recoverable/#finalize-recovery) recovery transactions directly using Social Recovery Module methods, which are public and callable once guardian signatures are collected and grace period requirements are met. diff --git a/docs/wallet/recovery/1-overview.mdx b/docs/wallet/recovery/1-overview.mdx index f1a68c4..266c9cc 100644 --- a/docs/wallet/recovery/1-overview.mdx +++ b/docs/wallet/recovery/1-overview.mdx @@ -27,14 +27,14 @@ Use this API to manage recovery requests approved by guardians, such as trusted The service collects and stores guardian signatures off-chain until the account's recovery threshold is met. For example, a 2-of-3 threshold requires approval from any two of the three guardians. -### Automatic Execution and Gas Sponsorship {#automatic-execution} +### Gas-Sponsored Execution and Finalization {#automatic-execution} -The service can be configured to submit two on-chain transactions automatically: +When enabled in the service configuration, the service submits and pays for two on-chain transactions on request: 1. **Execution:** Submit the approved recovery request once the guardian signature threshold is met. This starts the grace period. 2. **Finalization:** Complete the ownership change after the grace period ends. -The gas sponsorship relayer covers the costs of these transactions, so guardians do not need to hold funds or return to finalize recovery. +Your application triggers each step by calling `/recoveries/execute` and `/recoveries/finalize`. The service does not submit them on its own. The gas sponsorship relayer covers the transaction costs, so guardians do not need to hold funds. ### Social Engineering Protection @@ -42,7 +42,7 @@ Each recovery request has an emoji sequence that guardians can check with the ac ### Alerts and Notifications -Account owners can subscribe to email or SMS alerts when recovery is initiated, including requests submitted directly on-chain or through the service. The service monitors Social Recovery Module activity to detect these requests. +Account owners can subscribe to email or SMS alerts for on-chain recovery activity. The service monitors Social Recovery Module events, so it detects executions, finalizations, and cancellations whether they were submitted through the service or directly on-chain. Alerts are not sent when a request is created off-chain; the first alert is sent once the request is executed on-chain. See the [Recovery UX API reference](/wallet/recovery/ux-api/) for endpoints and request formats. @@ -63,11 +63,11 @@ See the [Email/SMS Recovery API reference](/wallet/recovery/auth-api/) for regis Once guardians are configured, a successful recovery follows these steps: -1. **Create a request.** A guardian submits a signed recovery request specifying the proposed new owners and their signing threshold. The service stores the request and alerts subscribed account owners. +1. **Create a request.** A guardian submits a signed recovery request specifying the proposed new owners and their signing threshold. The service stores the request off-chain. 2. **Collect approvals.** Guardians verify the request and submit signatures until the guardian threshold is met. -3. **Execute the request.** The approved request is submitted on-chain, starting the module's grace period. +3. **Execute the request.** Your application submits the approved request on-chain through `/recoveries/execute`, starting the module's grace period. Subscribed account owners are alerted at this point. 4. **Wait through the grace period.** The current owner can cancel an unauthorized recovery if they still control the account. -5. **Finalize recovery.** After the grace period, finalization replaces the account's owners and applies the new signing threshold. +5. **Finalize recovery.** After the grace period, your application calls `/recoveries/finalize`. Finalization replaces the account's owners and applies the new signing threshold. ```mermaid flowchart TD @@ -78,7 +78,7 @@ flowchart TD D -->|Current owner cancels| F[Recovery cancelled] ``` -Execution and finalization are automatic when enabled in the service configuration. **New owners gain control only after finalization.** +The service sponsors gas for execution and finalization when enabled in its configuration, but your application must trigger each step. **New owners gain control only after finalization.** ## Recovery Request States diff --git a/docs/wallet/recovery/2-ux-api.mdx b/docs/wallet/recovery/2-ux-api.mdx index 976e0bb..4527b2e 100644 --- a/docs/wallet/recovery/2-ux-api.mdx +++ b/docs/wallet/recovery/2-ux-api.mdx @@ -49,10 +49,10 @@ Examples use placeholder addresses, IDs, messages, and signatures. Replace them 1. **Create:** Submit a request with the first guardian's signature using `/recoveries/create`. 2. **Approve:** Other guardians check the request's emoji sequence with the account owner through a trusted channel, then submit signatures using `/recoveries/sign`. -3. **Execute:** Once the guardian threshold is met, execution starts the on-chain grace period. -4. **Finalize:** After the grace period, finalization applies the new owners and signing threshold. The current owner can cancel the recovery during the grace period. +3. **Execute:** Once the guardian threshold is met, call `/recoveries/execute` to submit the request on-chain and start the grace period. +4. **Finalize:** After the grace period, call `/recoveries/finalize` to apply the new owners and signing threshold. The current owner can cancel the recovery during the grace period. -The service can automate and sponsor execution and finalization when configured. Guardians can submit signatures off-chain without holding funds. Use the lookup endpoints to monitor progress; a successful submission does not by itself mean ownership has changed. See [request states](/wallet/recovery/overview/#recovery-request-states). +The service sponsors gas for execution and finalization when configured, but it does not submit them on its own: your application must call both endpoints. Guardians can submit signatures off-chain without holding funds. Use the lookup endpoints to monitor progress; a successful submission does not by itself mean ownership has changed. See [request states](/wallet/recovery/overview/#recovery-request-states). `newThreshold` is the signing threshold for the **new Safe owners** after recovery. The existing **guardian threshold** determines how many guardians must approve the recovery. @@ -361,48 +361,49 @@ Lists all recovery requests for a Safe account with advanced filtering, paginati curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \ -H 'Authorization: Bearer YOUR_BEARER_TOKEN' \ --data-urlencode "account=0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba" \ - --data-urlencode "chainId=1" + --data-urlencode "chainId=1" \ + --data-urlencode "limit=20" \ + --data-urlencode "offset=0" ``` ```json -{ - "recoveries": [ - { - "id": "123456789", - "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", - "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", - "chainId": 1, - "newOwners": [ - "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", - "0xB97A1C3993A551f0Febf030539630ACb77E6832D" - ], - "newThreshold": 2, - "nonce": "0x1", - "signatures": [ - [ - "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", - "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - ] - ], - "executeData": { - "sponsored": false, - "transactionHash": "" - }, - "finalizeData": { - "sponsored": false, - "transactionHash": "" - }, - "status": "PENDING", - "discoverable": true, - "createdAt": "2023-04-18T12:34:56.789Z", - "updatedAt": "2023-04-18T12:34:56.789Z" - } - ], - "total": 1 -} +[ + { + "id": "123456789", + "emoji": "πŸ€–πŸ˜…πŸ₯΅πŸ‘»πŸ––", + "account": "0xD422B9d638a7BA4eBeF9e33Af9456007eAB4ccba", + "chainId": 1, + "newOwners": [ + "0x41153290c995c8c4410d50f95D87ee86A1B07eeC", + "0xB97A1C3993A551f0Febf030539630ACb77E6832D" + ], + "newThreshold": 2, + "nonce": "0x1", + "signatures": [ + [ + "0x795B9cD1E5419C54B07768d4AD09809407dfAF5b", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + ] + ], + "executeData": { + "sponsored": false, + "transactionHash": "" + }, + "finalizeData": { + "sponsored": false, + "transactionHash": "" + }, + "status": "PENDING", + "discoverable": true, + "createdAt": "2023-04-18T12:34:56.789Z", + "updatedAt": "2023-04-18T12:34:56.789Z" + } +] ``` + +The response is a plain array. There is no total count; request the next page by increasing `offset` until fewer than `limit` items are returned. @@ -682,9 +683,9 @@ Use the HTTP status to determine whether a request succeeded. Service errors con | Code | Description | |------|-------------| | 200 | Success | -| 400 | Bad Request - Invalid parameters or missing required fields | +| 400 | Bad Request - Invalid parameters, missing required fields, or an invalid guardian signature | | 401 | Unauthorized - Invalid or missing Bearer token | -| 403 | Forbidden - Signature verification failed, challenge invalid or expired, or action not permitted | +| 403 | Forbidden - SIWE signature verification failed, challenge invalid or expired, sponsorship not enabled for the network, or the request is not ready to execute (wrong nonce or insufficient signatures) | | 404 | Not Found - Resource not found | | 409 | Conflict - The alert subscription already exists | | 429 | Too Many Requests - Rate limit exceeded | diff --git a/src/data/safeRecoveryService.ts b/src/data/safeRecoveryService.ts index 666c4f8..35d1955 100644 --- a/src/data/safeRecoveryService.ts +++ b/src/data/safeRecoveryService.ts @@ -67,7 +67,7 @@ const recoveryRequestSchema = [ description: "The transaction hash of the finalization execution", }, ], - description: "An object field representing the finalization recovery transaction", + description: "Finalization transaction details", }, { key: "status", @@ -221,32 +221,6 @@ export const postRecoveriesFinalizeByIdResponse = [ }, ]; -export const postAlertSubscribe = [ - { - key: "account", - type: "string", - description: "The Safe account address that should be monitored. Must be a valid Ethereum address", - }, - { - key: "email", - type: "string", - description: "The email target to receive those alerts. Must be a valid email address.", - }, - { - key: "signature", - type: "string", - description: "A signature from the account containing the email and a nonce", - }, -]; - -export const postAlertSubscribeResponse = [ - { - key: "success", - type: "true", - description: "Return true once finilized. Else returns error", - }, -]; - export const getRecoveriesListByAddress = [ { key: "account", @@ -273,28 +247,43 @@ export const getRecoveriesListByAddress = [ type: "boolean", description: "(Optional) Filter by finalized status. Cross-checked with indexed data", }, + { + key: "nonce", + type: "string", + description: "(Optional) Filter by recovery nonce as a hex string. Use nonce__lt, nonce__gt, nonce__lte, or nonce__gte for range comparisons", + }, + { + key: "createdAt", + type: "string", + description: "(Optional) Filter by creation time as an ISO 8601 date. Use createdAt__lt, createdAt__gt, createdAt__lte, or createdAt__gte for range comparisons", + }, { key: "orderBy", type: "string", - description: "(Optional) Field to order by (e.g., 'createdAt', 'nonce')", + description: "(Optional) Field to order by: 'createdAt' or 'nonce'", }, { key: "order", type: "string", description: "(Optional) Order direction: 'asc' or 'desc'. Defaults to 'desc'", }, + { + key: "limit", + type: "number", + description: "(Optional) Maximum number of requests to return, between 1 and 50. Defaults to 20", + }, + { + key: "offset", + type: "number", + description: "(Optional) Number of requests to skip for pagination. Defaults to 0", + }, ]; export const getRecoveriesListByAddressResponse = [ { - key: "recoveries", + key: "RecoveryRequest[]", type: recoveryRequestSchema, - description: "A list of Recovery Requests matching the filters", - }, - { - key: "total", - type: "number", - description: "Total number of recovery requests matching the filters", + description: "A list of Recovery Requests matching the filters, as a plain array", }, ]; From ca056a2fff896d610933c52fd5a4020db6ec2d19 Mon Sep 17 00:00:00 2001 From: sednaoui Date: Tue, 22 Sep 2026 18:22:40 +0200 Subject: [PATCH 3/3] docs: clarify recovery pagination limits and sponsorship metadata --- docs/wallet/recovery/2-ux-api.mdx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/wallet/recovery/2-ux-api.mdx b/docs/wallet/recovery/2-ux-api.mdx index 4527b2e..15ccac2 100644 --- a/docs/wallet/recovery/2-ux-api.mdx +++ b/docs/wallet/recovery/2-ux-api.mdx @@ -1,6 +1,6 @@ --- title: Safe Recovery UX API Reference -description: API specification for Candide's Safe Recovery Service. Features signature aggregation, gas sponsorship, recovery request monitoring, signature storage, and auto-execution. +description: API specification for Candide's Safe Recovery Service. Features signature aggregation and storage, recovery request monitoring, and gas-sponsored execution and finalization. keywords: [safe recovery, social recovery, smart wallet, email recovery, guardian recovery] --- @@ -403,7 +403,9 @@ curl -G "https://yourcompany.recovery.candide.dev/recoveries/listByAddress" \ ] ``` -The response is a plain array. There is no total count; request the next page by increasing `offset` until fewer than `limit` items are returned. +The response is a plain array with no total count. Request each next page by increasing `offset` by `limit`. + +For queries without the `executed` and `finalized` filters, stop when fewer than `limit` items are returned. These two filters are applied after pagination, so queries using either filter can return a short or empty page even when later pages contain matching requests. For those queries, response length does not reliably indicate the end of the results.