info

Looking for a simpler approach? Candide offers a managed [Email / SMS Recovery](https://docs.candide.dev/wallet/plugins/add-candide-guardian.md) service that handles key management for you.

# Use Turnkey with AbstractionKit

[Turnkey](https://turnkey.com) offers a flexible private key management solution leveraging AWS Nitro Systems to secure user keys.

Fully leverage Account Abstraction by combining Turnkey with AbstractionKit to enable a trusted recoverer. Enabling the Account Recovery Module requires an on-chain transaction. For user convenience, batch recovery transactions and sponsor gas fees.

Relevant links for additional information during this guide:

* [High level explanation: How on-chain guardian recovery works](https://docs.candide.dev/wallet/plugins/recovery-with-guardians/)
* [SDK: Guardian Recovery References](https://docs.candide.dev/blog/making-accounts-recoverable/)
* [Turnkey Docs](https://docs.turnkey.com)
* [Code Example on GitHub](https://github.com/candidelabs/abstractionkit-examples/blob/main/recovery/recovery.ts)

## Installation[​](#installation "Direct link to Installation")

### Install required dependencies[​](#install-required-dependencies "Direct link to Install required dependencies")

* ethers
* viem

```
npm i abstractionkit && @turnkey/http && @turnkey/api-key-stamper && @turnkey/ethers
```

```
npm i abstractionkit && @turnkey/http && @turnkey/api-key-stamper && @turnkey/viem
```

### Create a Turnkey account[​](#create-a-turnkey-account "Direct link to Create a Turnkey account")

Create an account and get the API keys on [Turnkey's Dashboard](https://app.turnkey.com). You will need:

* Organization ID
* Public API Key
* Private API Key
* Create a private/public key pair on Turnkey dashboard. This is Guardian Account.

### Configure .env file[​](#configure-env-file "Direct link to Configure .env file")

Configure the values you created from Turnkey dashboard in an .env file, along with a node endpoint.

```
// turnkey guardian

TURNKEY_PUBLIC_KEY=

TURNKEY_PRIVATE_KEY= // For this demo, we exported the private key of the guardian account. In production, use Turnkey api

TURNKEY_ORG_ID=

TURNKEY_WALLET_ADDRESS=



// candide

BUNDLER_URL=

JSON_RPC_NODE_PROVIDER=

OWNER_PUBLIC_ADDRESS=

NEW_ONWER_PUBLIC_ADDRESS=
```

## Setup Guardian[​](#setup-guardian "Direct link to Setup Guardian")

This step shows how to contrust the calldata for creating the userop to add a guardian.

```
import { SocialRecoveryModule } from "abstractionkit";



const srm = new SocialRecoveryModule();



const enableModuleTx = srm.createEnableModuleMetaTransaction(

    smartAccount.accountAddress

);

const addGuardianTx = srm.createAddGuardianWithThresholdMetaTransaction(

    smartAccount.accountAddress, 

    process.env.TURNKEY_PUBLIC_KEY, // Turnkey Guardian Address

    1n //threshold

);



let userOperation = await smartAccount.createUserOperation(

    [enableModuleTx, addGuardianTx],

    process.env.JSON_RPC_NODE_PROVIDER,

    process.env.BUNDLER_URL,

) 
```

## Initiate Recovery[​](#initiate-recovery "Direct link to Initiate Recovery")

* ethers
* viem

```
import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit";



import { TurnkeySigner } from "@turnkey/ethers";

import { TurnkeyClient } from "@turnkey/http";

import { ApiKeyStamper } from "@turnkey/api-key-stamper";



const turnkeyClient = new TurnkeyClient(

  { baseUrl: "https://api.turnkey.com" },

  new ApiKeyStamper({

    apiPublicKey: process.env.TURNKEY_PUBLIC_KEY as string,

    apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY as string,

  })

);

const guardianSigner = new TurnkeySigner({

  client: turnkeyClient,

  organizationId: process.env.TURNKEY_ORG_ID as string,

  signWith: process.env.TURNKEY_WALLET_ADDRESS as string,  // For this demo, we manually generated the signer on turnkey dashboard

});



const initiateRecoveryMetaTx = createConfirmRecoveryMetaTransaction(

    smartAccount.address, 

    [process.env.NEW_ONWER_PUBLIC_ADDRESS],

    1, // new threshold

    true, // whether to auto-start execution of recovery

)



// make sure to fund the guardian address on turnkey

const sendTx = guardianSigner.sendTransaction({

    to: initiateRecoveryMetaTx.to,

    data: initiateRecoveryMetaTx.data,

    value: 0,

});
```

```
import { SafeMultiChainSigAccountV1 as SafeAccount } from "abstractionkit";

import { createWalletClient, http } from "viem";



import { TurnkeyClient } from "@turnkey/http";

import { ApiKeyStamper } from "@turnkey/api-key-stamper";

import { createAccount } from "@turnkey/viem";



const turnkeyClient = new TurnkeyClient(

  { baseUrl: "https://api.turnkey.com" },

  new ApiKeyStamper({

    apiPublicKey: process.env.TURNKEY_PUBLIC_KEY as string,

    apiPrivateKey: process.env.TURNKEY_PRIVATE_KEY as string,

  }),

);

const turnkeyAccount = await createAccount({

  client: turnkeyClient,

  organizationId: process.env.TURNKEY_ORG_ID as string,

  signWith: process.env.TURNKEY_WALLET_ADDRESS as string, // for this example, we manually generated the signer on turnkey dashboard

});



const guardianSigner = createWalletClient({

  account: turnkeyAccount,

  transport: http(process.env.JSON_RPC_NODE_PROVIDER as string),

});



const initiateRecoveryMetaTx = createConfirmRecoveryMetaTransaction(

    smartAccount.address, 

    [process.env.NEW_ONWER_PUBLIC_ADDRESS],

    1, // new threshold

    true, // whether to auto-start execution of recovery

)



// make sure to fund the guardian address on turnkey

const sendTx1 = await guardianSigner.sendTransaction({

    to: initiateRecoveryMetaTx.to,

    data: initiateRecoveryMetaTx.data,

});
```

## Finilize Recovery[​](#finilize-recovery "Direct link to Finilize Recovery")

Wait for recovery period to pass before finilizing

* ethers
* viem

```
const finalizeRecoveryMetaTx = createFinalizeRecoveryMetaTransaction(smartAccount.accountAddress)

    

// Anyone can call the finilize function after the grace period is over

const sendTx2 = await guardianSigner.sendTransaction({

    to: finalizeRecoveryMetaTx.to,

    data: finalizeRecoveryMetaTx.data,

})
```

```
const finalizeRecoveryMetaTx = createFinalizeRecoveryMetaTransaction(smartAccount.accountAddress)



// Anyone can call the finilize function after the grace period is over

const sendTx2 = await guardianSigner.sendTransaction({

    to: finalizeRecoveryMetaTx.to,

    data: finalizeRecoveryMetaTx.data,

})
```
