Use Turnkey with AbstractionKit
Turnkey 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
- 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 Turnkey 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 Turnkey dashboard. This is Guardian Account.
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
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 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
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,
})