Use Turnkey with AbstractionKit
Turkey offers a flexible private key management solution that leverages AWS Nitros Systems to secure users keys.
To fully leverage the potential of Account Abstraction, consider combining Turnkey with AbstractionKit to enable a trusted recoverer. Enabling the Account recovery module will require an onchain transaction. To add convenience for your users, you can batch the recovery transactions and sponsor their gas fees.
Relevant links for additional information during this guide:
- High level explanation: How on-chain guardian recovery works
- SDK: Guardian Recovery References
- Turnkey Docs
- Code Example on GitHub
Installation
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 Turkey account
Create an account and get the API keys on Turnkey's Dashboard. You will need:
- Organization ID
- Public API Key
- Private API Key
- Create a private/public key pair on Turkey dashboard. This is Guardian Account.
Configure .env file
Configure the values you created from Turkey 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 turkey api
TURNKEY_ORG_ID=
TURNKEY_WALLET_ADDRESS=
// candide
BUNDLER_URL=
JSON_RPC_NODE_PROVIDER=
OWNER_PUBLIC_ADDRESS=
NEW_ONWER_PUBLIC_ADDRESS=
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
- ethers
- viem
import { SafeAccountV0_3_0 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 { SafeAccountV0_3_0 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 turkeyAccount = 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: turkeyAccount,
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
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,
})