Skip to content
This repository was archived by the owner on Feb 11, 2025. It is now read-only.

Commit a6de240

Browse files
committed
feat: allow recovery of undeployed accounts
1 parent 2084724 commit a6de240

File tree

9 files changed

+130
-19
lines changed

9 files changed

+130
-19
lines changed

actions/transferAll/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export async function transferAll(acc: Account, newAddress: string, ora: Ora) {
5454
return {
5555
...c,
5656
calldata: compileCalldata({
57-
to: newAddress,
57+
to: newAddress.toLowerCase(),
5858
value: {
5959
type: "struct",
6060
...uint256.bnToUint256(

execute.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,9 @@ export async function execute(account: Account, call: Call[] | Call) {
4747
const a = new OldAccount(oldProvider, lowerCaseAddress, keyPair);
4848
return await a.execute(calls);
4949
} catch (e) {
50-
console.warn("old failed", e);
5150
const newProvider = new NewProvider({ network: account.networkId as any });
5251
const a = new NewAccount(newProvider, lowerCaseAddress, keyPair);
5352
return a.execute(calls).catch((e) => {
54-
console.warn("new failed", e);
5553
throw e;
5654
});
5755
}

getAccounts.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import { Wallet } from "ethers";
22
import { ec, hash, number, SequencerProvider, stark } from "starknet";
3+
import { getBalances } from "./getTokenBalance";
34
import { getPathForIndex, getStarkPair } from "./keyDerivation";
45

56
const CHECK_OFFSET = 10;
67

7-
const PROXY_CONTRACT_CLASS_HASHES = [
8+
export const PROXY_CONTRACT_CLASS_HASHES = [
89
"0x25ec026985a3bf9d0cc1fe17326b245dfdc3ff89b8fde106542a3ea56c5a918",
910
];
1011
const ARGENT_ACCOUNT_CONTRACT_CLASS_HASHES = [
12+
"0x1a7820094feaf82d53f53f214b81292d717e7bb9a92bb2488092cd306f3993f",
13+
"0x7e28fb0161d10d1cf7fe1f13e7ca57bce062731a3bd04494dfd2d0412699727",
1114
"0x3e327de1c40540b98d05cbcb13552008e36f0ec8d61d46956d2f9752c294328",
15+
"0x33434ad846cdd5f23eb73ff09fe6fddd568284a0fb7d1be20ee482f044dabe2",
1216
];
1317

1418
export const BASE_DERIVATION_PATHS = [
@@ -41,12 +45,24 @@ async function getAccountByKeyPair(
4145
);
4246

4347
try {
48+
const ethBalance = await getBalances([address], network, ["ETH"]);
49+
50+
if (ethBalance[0].rawBalance !== "0") {
51+
return {
52+
address,
53+
deployImplementation: accountClassHash,
54+
networkId: network,
55+
privateKey: number.toHex(number.toBN(keyPair.getPrivate().toString())),
56+
};
57+
}
58+
4459
const code = await provider.getCode(address);
4560

4661
if (code.bytecode.length > 0) {
4762
return {
4863
address,
4964
networkId: network,
65+
deployImplementation: accountClassHash,
5066
privateKey: number.toHex(number.toBN(keyPair.getPrivate().toString())),
5167
};
5268
}
@@ -72,6 +88,7 @@ export async function getAccountsBySeedPhrase(
7288

7389
const accounts: {
7490
address: string;
91+
deployImplementation?: string;
7592
networkId: string;
7693
derivationPath: string;
7794
privateKey: string;
@@ -129,6 +146,7 @@ export async function getAccountsByPrivateKey(
129146

130147
const accounts: {
131148
address: string;
149+
deployImplementation?: string;
132150
networkId: string;
133151
privateKey?: string;
134152
}[] = [];

getTokenBalance.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ import { Multicall } from "@argent/x-multicall";
55

66
export async function getBalances(
77
addresses: string[],
8-
network: "mainnet-alpha" | "goerli-alpha"
8+
network: "mainnet-alpha" | "goerli-alpha",
9+
tokenWhiteList: string[] = []
910
) {
10-
const tokens = TOKENS.filter((token) => token.network === network);
11+
const tokens = TOKENS.filter((token) => token.network === network).filter(
12+
(token) => {
13+
if (tokenWhiteList.length) {
14+
return tokenWhiteList.includes(token.symbol);
15+
} else {
16+
return true;
17+
}
18+
}
19+
);
1120
const tokenAddresses = tokens.map((token) => token.address);
1221
const provider = new SequencerProvider({ network });
1322
const multicallProvider = new Multicall(provider as any);
@@ -40,18 +49,20 @@ export async function getBalances(
4049

4150
return addressesTokensCombinations.map((addressToken, index) => {
4251
const balance = results[index];
52+
const rawBalance = balance
53+
? uint256
54+
.uint256ToBN({
55+
low: encode.addHexPrefix(balance[0]),
56+
high: encode.addHexPrefix(balance[1]),
57+
})
58+
.toString()
59+
: "0";
4360
return {
4461
address: addressToken.address,
4562
token: addressToken.token,
63+
rawBalance,
4664
balance: formatTokenBalance(
47-
balance
48-
? uint256
49-
.uint256ToBN({
50-
low: encode.addHexPrefix(balance[0]),
51-
high: encode.addHexPrefix(balance[1]),
52-
})
53-
.toString()
54-
: "0",
65+
rawBalance,
5566
tokens.find((x) => x.address === addressToken.token)!.decimals
5667
),
5768
};

issues/deploy/detect.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Account } from "../../ui/pickAccounts";
2+
3+
export const detect = async (accounts: Account[]): Promise<string[]> => {
4+
return accounts
5+
.filter(
6+
({ signer, implementation, version }) =>
7+
signer === null && implementation === null && version === null
8+
)
9+
.map(({ address }) => address);
10+
};

issues/deploy/fix.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import ora from "ora";
2+
import {
3+
Account as SNAccount,
4+
ec,
5+
SequencerProvider,
6+
stark,
7+
hash,
8+
} from "starknet-490";
9+
import { PROXY_CONTRACT_CLASS_HASHES } from "../../getAccounts";
10+
import { getVersion } from "../../getVersion";
11+
import { oraLog } from "../../oraLog";
12+
import { Account } from "../../ui/pickAccounts";
13+
14+
export const fix = async (
15+
accounts: Account[],
16+
network: "mainnet-alpha" | "goerli-alpha",
17+
accountsToRecover: string[]
18+
): Promise<void> => {
19+
const spinner = ora(`Deploying accounts (this may take some time)`).start();
20+
const provider = new SequencerProvider({ network });
21+
22+
for (const address of accountsToRecover) {
23+
const account = accounts.find((a) => a.address === address);
24+
if (!account) {
25+
throw new Error(`Account ${address} not found`);
26+
}
27+
if (!account.deployImplementation) {
28+
throw new Error(`Account ${address} has no deployImplementation`);
29+
}
30+
const keyPair = ec.getKeyPair(account.privateKey);
31+
const starkKey = ec.getStarkKey(keyPair);
32+
const snAccount = new SNAccount(provider, account.address, keyPair);
33+
34+
const constructorCallData = {
35+
implementation: account.deployImplementation,
36+
selector: hash.getSelectorFromName("initialize"),
37+
calldata: stark.compileCalldata({ signer: starkKey, guardian: "0" }),
38+
};
39+
40+
const deployAccountPayload = {
41+
classHash: PROXY_CONTRACT_CLASS_HASHES[0],
42+
contractAddress: account.address,
43+
constructorCalldata: stark.compileCalldata(constructorCallData),
44+
addressSalt: starkKey,
45+
};
46+
47+
const transaction = await snAccount.deployAccount(deployAccountPayload);
48+
oraLog(spinner, `Transaction ${transaction.transaction_hash} created`);
49+
await provider.waitForTransaction(transaction.transaction_hash);
50+
51+
if (account) {
52+
account.signer = starkKey;
53+
account.implementation = account.deployImplementation;
54+
const [newVersion] = await getVersion(
55+
[account.address],
56+
account.networkId as any
57+
);
58+
account.version = newVersion;
59+
}
60+
61+
// wait 1 minute extra to make sure the transaction is mined
62+
await new Promise((resolve) => setTimeout(resolve, 60000));
63+
}
64+
spinner.succeed(`Deployed accounts`);
65+
};

issues/index.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,39 @@ import { Account } from "../ui/pickAccounts";
22
import { detect as detectOldHashAlgo } from "./oldHashAlgo/detect";
33
import { fix as fixOldHashAlgo } from "./oldHashAlgo/fix";
44
import { detect as detectSigner0 } from "./signer0/detect";
5+
import { detect as detectDeploy } from "./deploy/detect";
56
import { fix as fixSigner0 } from "./signer0/fix";
7+
import { fix as fixDeploy } from "./deploy/fix";
68

79
interface IssuesMap {
810
oldHashAlgo?: string[];
911
signer0?: string[];
12+
deploy?: string[];
1013
}
1114

1215
export async function detectAccountIssues(
1316
accounts: Account[]
1417
): Promise<IssuesMap> {
1518
const oldHashAlgo = await detectOldHashAlgo(accounts);
1619
const signer0 = await detectSigner0(accounts);
17-
return { oldHashAlgo, signer0 };
20+
const deploy = await detectDeploy(accounts);
21+
return { oldHashAlgo, signer0, deploy };
1822
}
1923

2024
export async function fixAccountIssues(
2125
accounts: Account[],
2226
network: "mainnet-alpha" | "goerli-alpha",
2327
issues: IssuesMap
2428
): Promise<void> {
25-
const { oldHashAlgo } = issues;
29+
const { oldHashAlgo, signer0, deploy } = issues;
30+
31+
if (deploy?.length && deploy?.length > 0) {
32+
await fixDeploy(accounts, network, deploy);
33+
}
2634
if (oldHashAlgo?.length && oldHashAlgo?.length > 0) {
2735
await fixOldHashAlgo(accounts, network, oldHashAlgo);
2836
}
29-
if (issues.signer0?.length && issues.signer0?.length > 0) {
30-
await fixSigner0(accounts, network, issues.signer0);
37+
if (signer0?.length && signer0?.length > 0) {
38+
await fixSigner0(accounts, network, signer0);
3139
}
3240
}

issues/signer0/detect.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ import { Account } from "../../ui/pickAccounts";
33

44
export const detect = async (accounts: Account[]): Promise<string[]> => {
55
return accounts
6-
.filter(({ signer }) => BigNumber.from(signer).eq(0))
6+
.filter(({ signer }) => signer && BigNumber.from(signer).eq(0))
77
.map(({ address }) => address);
88
};

ui/pickAccounts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface BaseAccount {
99
version: string | null;
1010
privateKey?: string;
1111
derivationPath?: string;
12+
deployImplementation?: string;
1213
}
1314

1415
export interface Account extends BaseAccount {

0 commit comments

Comments
 (0)