Skip to main content

Recovery

info

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.

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:

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.

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:

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:

  1. Set deviceKp as the primary account key. Please note that the master key belonging to accountKp will be locked. deviceKp should be used as a primary signer instead.
  2. Set all operation thresholds to 10. You can read more about threshold in the documentation
  3. Use identities that were defined earlier on both servers. (That means, both server will accept SEP-10 authentication via recoveryKp as an auth method)
  4. 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:

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:

const authToken = await recovery
.sep10Auth(server1Key)
.authenticate({ accountKp: recoveryKp });

Next, get account info using auth tokens:

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.

// 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:

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.

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:

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:

  1. It's the only signer that's not in serverAuth.
  2. 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:

await stellar.submitTransaction(recoverTxn);