Recovery
This guide is available on three different programming languages: Typescript, Kotlin and Flutter (Dart). You can change the shown version on each page via the buttons above.
The Sep-30 standard defines the standard way for an individual (e.g., a user or wallet) to regain access to their Stellar account after losing its private key without providing any third party control of the account. During this flow the wallet communicates with one or more recovery signer servers to register the wallet for a later recovery if it's needed.
Create Recoverable Account
First, let's create an account key, a device key, and a recovery key that will be attached to the account.
- TypeScript
const accountKp = wallet.stellar().account().createKeypair();
const deviceKp = wallet.stellar().account().createKeypair();
const recoveryKp = wallet.stellar().account().createKeypair();
The accountKp
is the wallet's main account. The deviceKp
we will be adding to the wallet as a signer so a device (eg. a mobile device a wallet is hosted on) can take control of the account. And the recoveryKp
will be used to identify the key with the recovery servers.
Next, let's identify the recovery servers and create our recovery object:
- TypeScript
const server1Key = "server1";
const server1 = {
endpoint: "recovery-example.com",
authEndpoint: "auth-example.com",
homeDomain: "test-domain",
};
const server2Key = "server2";
const server2 = {
endpoint: "recovery-example2.com",
authEndpoint: "auth-example2.com",
homeDomain: "test-domain2",
};
const recovery = wallet.recovery({
servers: { [server1Key]: server1, [server2Key]: server2 },
});
Next, we need to define SEP-30 identities. In this example we are going to create an identity for both servers. Registering an identity tells the recovery server what identities are allowed to access the account.
- TypeScript
const identity1 = {
role: RecoveryRole.OWNER,
authMethods: [
{
type: RecoveryType.STELLAR_ADDRESS,
value: recoveryKp.publicKey,
},
],
};
const identity2 = {
role: RecoveryRole.OWNER,
authMethods: [
{
type: RecoveryType.EMAIL,
value: "[email protected]",
},
],
};
Here, stellar key and email are used as recovery methods. Other recovery servers may support phone as a recovery method as well.
You can read more about SEP-30 identities here
Next, let's create a recoverable account:
- TypeScript
const config = {
accountAddress: accountKp,
deviceAddress: deviceKp,
accountThreshold: { low: 10, medium: 10, high: 10 },
accountIdentity: { [server1Key]: [identity1], [server2Key]: [identity2] },
signerWeight: { device: 10, recoveryServer: 5 },
};
const recoverableWallet = await recovery.createRecoverableWallet(config);
With the given parameters, this function will create a transaction that will:
- Set
deviceKp
as the primary account key. Please note that the master key belonging toaccountKp
will be locked.deviceKp
should be used as a primary signer instead. - Set all operation thresholds to 10. You can read more about threshold in the documentation
- Use identities that were defined earlier on both servers. (That means, both server will accept SEP-10 authentication via
recoveryKp
as an auth method) - Set device key weight to 10, and recovery server weight to 5. Given these account thresholds, both servers must be used to recover the account, as transaction signed by one will only have weight of 5, which is not sufficient to change account key.
Finally, sign and submit transaction to the network:
- TypeScript
recoverableWallet.transaction.sign(accountKp.keypair);
await stellar.submitTransaction(recoverableWallet.transaction);
Get Account Info
You can fetch account info from one or more servers. To do so, first we need to authenticate with a recovery server using the SEP-10 authentication method:
- TypeScript
const authToken = await recovery
.sep10Auth(server1Key)
.authenticate({ accountKp: recoveryKp });
Next, get account info using auth tokens:
- TypeScript
const accountResp = await recovery.getAccountInfo(accountKp, {
[server1Key]: authToken,
});
Our second identity uses an email as an auth method. For that we can't use a [SEP-10] auth token for that server. Instead we need to use a token that ties the email to the user. For example, Firebase tokens are a good use case for this. To use this, the recovery signer server needs to be prepared to handle these kinds of tokens.
Getting account info using these tokens is the same as before.
- TypeScript
// get token from firebase
const firebaseToken = AuthToken.from(<firebase token string>)
const accountResp = await recovery.getAccountInfo(accountKp, {
[server2Key]: firebaseToken,
});
Recover Wallet
Let's say we've lost our device key and need to recover our wallet.
First, we need to authenticate with both recovery servers:
- TypeScript
const authToken1 = await recovery
.sep10Auth(server1Key)
.authenticate({ accountKp: recoveryKp });
// get firebase token using firebase
const firebaseToken = AuthToken.from(<firebase token string>)
We need to know the recovery signer addresses that will be used to sign the transaction. You can get them from either the recoverable wallet object we created earlier (recoverableWallet.signers
), or via fetching account info from recovery servers.
- TypeScript
const recoverySignerAddress1 = recoverableWallet.signers[0];
const recoverySignerAddress2 = recoverableWallet.signers[1];
Next, create a new device key and retrieve a signed transaction that replaces the device key:
- TypeScript
const newDeviceKp = accountService.createKeypair();
const serverAuth = {
[server1Key]: {
signerAddress: recoverySignerAddress1,
authToken1,
},
[server2Key]: {
signerAddress: recoverySignerAddress2,
firebaseToken,
},
};
const recoverTxn = await recovery.replaceDeviceKey(
accountKp,
newDeviceKp,
serverAuth,
);
Calling this function will create a transaction that locks the previous device key and replaces it with your new key (having the same weight as the old one). Both recovery signers will have signed the transaction.
The lost device key is deduced automatically if not given. A signer will be considered a device key, if one of these conditions matches:
- It's the only signer that's not in
serverAuth
. - All signers in
serverAuth
have the same weight, and the potential signer is the only one with a different weight.
Note that the account created above will match the first criteria. If 2-3 schema were used, then second criteria would match. (In 2-3 schema, 3 serves are used and 2 of them is enough to recover key. This is a recommended approach.)
Note: you can also use more low-level signWithRecoveryServers
functions to sign arbitrary transaction.
Finally, it's time to submit the transaction:
- TypeScript
await stellar.submitTransaction(recoverTxn);