Skip to content

Commit

Permalink
release
Browse files Browse the repository at this point in the history
  • Loading branch information
janek26 committed Sep 15, 2022
0 parents commit a27adb5
Show file tree
Hide file tree
Showing 30 changed files with 3,932 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
yarn-error.log
bin
12 changes: 12 additions & 0 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Starknet Recovery CLI

This is a command line tool can be used to recover funds from old Starknet accounts.
After entering your seedphrase or private key it will scan for your accounts and potential funds and issues.
You can then choose to fix issues and recover your funds to a new account address.

## Usage

It is simplest to download the latest release from the [releases page](https://github.com/argentlabs/argent-starknet-recover/releases).
After downloading the binary matching your machine you can run it inside a terminal.

If you have Node installed you can also clone this repo and run `yarn && yarn start` to run the CLI tool.
31 changes: 31 additions & 0 deletions abi/Multicall.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[
{
"inputs": [
{
"name": "calls_len",
"type": "felt"
},
{
"name": "calls",
"type": "felt*"
}
],
"name": "aggregate",
"outputs": [
{
"name": "block_number",
"type": "felt"
},
{
"name": "result_len",
"type": "felt"
},
{
"name": "result",
"type": "felt*"
}
],
"stateMutability": "view",
"type": "function"
}
]
23 changes: 23 additions & 0 deletions actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import prompts from "prompts";

export async function selectAction(issuesAmount: number) {
const { action }: { action: "transferAll" | "fixIssues" } = await prompts({
type: "select",
name: "action",
message: "Select action",
choices: [
{ title: "Transfer all tokens", value: "transferAll" },
{
title: "Fix issues",
value: "fixIssues",
disabled: issuesAmount === 0,
description:
issuesAmount === 0
? "No issues to fix"
: `${issuesAmount} issues to fix`,
},
],
});

return action;
}
91 changes: 91 additions & 0 deletions actions/transferAll/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { BigNumber, utils } from "ethers";
import {
Account as SNAccount,
ec,
number,
SequencerProvider,
uint256,
} from "starknet";
import { compileCalldata } from "starknet/dist/utils/stark";
import { Account } from "../../ui/pickAccounts";
import TOKENS from "../../default-tokens.json";
import { Ora } from "ora";
import { oraLog } from "../../oraLog";

export async function transferAll(acc: Account, newAddress: string, ora: Ora) {
const { privateKey } = acc;
if (!privateKey) {
throw new Error("No private key for account to credit");
}
const keyPair = ec.getKeyPair(privateKey);
const starkKey = ec.getStarkKey(keyPair);
if (!BigNumber.from(acc.signer).eq(starkKey)) {
throw new Error(
"Account cant be controlled with the selected private key or seed"
);
}

const provider = new SequencerProvider({ network: acc.networkId as any });
const account = new SNAccount(provider, acc.address.toLowerCase(), keyPair);
const tokens = TOKENS.filter((t) => t.network === acc.networkId);
const calls = Object.entries(acc.balances)
.filter(([, balance]) => utils.parseEther(balance).gt(0))
.map(([token, balance]) => {
const tokenDetails = tokens.find((t) => t.address === token);
return {
contractAddress: token,
entrypoint: "transfer",
calldata: compileCalldata({
to: newAddress.toLowerCase(),
value: {
type: "struct",
...uint256.bnToUint256(
number.toBN(
utils
.parseUnits(balance, tokenDetails?.decimals || 18)
.toString()
)
),
},
}),
};
});

if (calls.length) {
const { suggestedMaxFee } = await account.estimateFee(calls);

const callsWithFee = calls.map((c) => {
const tokenDetails = tokens.find((t) => t.symbol === "ETH");
if (c.contractAddress === tokenDetails?.address) {
const balance = acc.balances[c.contractAddress];
return {
...c,
calldata: compileCalldata({
to: newAddress,
value: {
type: "struct",
...uint256.bnToUint256(
number.toBN(
utils
.parseUnits(balance, tokenDetails?.decimals ?? 18)
.sub(number.toHex(suggestedMaxFee))
.toString()
)
),
},
}),
};
}
return c;
});

// execute with suggested max fee substracted from a potential eth transfer
const transaction = await account.execute(callsWithFee, undefined, {
maxFee: suggestedMaxFee,
});

oraLog(ora, `Transaction ${transaction.transaction_hash} created`);

await provider.waitForTransaction(transaction.transaction_hash);
}
}
38 changes: 38 additions & 0 deletions actions/transferAll/ui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import ora from "ora";
import prompts from "prompts";
import { ValidationError } from "yup";
import { addressSchema } from "../../schema";
import { Account } from "../../ui/pickAccounts";
import { transferAll } from "./core";

export async function showTransferAll(accounts: Account[]) {
const { toAddress } = await prompts(
{
type: "text",
name: "toAddress",
message: "Enter the address to transfer to",
validate: async (value: string) => {
try {
await addressSchema.validate(value);
} catch (e) {
return `Invalid address '${value}'.${
e instanceof ValidationError ? ` Reason: ${e.message}` : ""
}`;
}

return true;
},
},
{
onCancel: () => process.exit(0),
}
);

const spinner = ora("Transferring all tokens").start();

await Promise.all(
accounts.map(async (acc) => transferAll(acc, toAddress, spinner))
);

spinner.succeed("All tokens transferred");
}
15 changes: 15 additions & 0 deletions addressFormatting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { encode } from "starknet";

export const formatAddress = (address: string) =>
encode.addHexPrefix(encode.removeHexPrefix(address).padStart(64, "0"));

export const truncateAddress = (address: string) => {
return truncateHex(formatAddress(address));
};

export const truncateHex = (value: string) => {
const hex = value.slice(0, 2);
const start = value.slice(2, 6);
const end = value.slice(-4);
return `${hex}${start}...${end}`;
};
102 changes: 102 additions & 0 deletions default-tokens.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
[
{
"address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"name": "Ether",
"symbol": "ETH",
"decimals": "18",
"network": "mainnet-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/eth.png",
"showAlways": true
},
{
"address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"name": "Ether",
"symbol": "ETH",
"decimals": "18",
"network": "goerli-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/eth.png",
"showAlways": true
},
{
"address": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
"name": "Ether",
"symbol": "ETH",
"decimals": "18",
"network": "integration",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/eth.png",
"showAlways": true
},
{
"address": "0x62230ea046a9a5fbc261ac77d03c8d41e5d442db2284587570ab46455fd2488",
"name": "Ether",
"symbol": "ETH",
"decimals": "18",
"network": "localhost",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/eth.png",
"showAlways": true
},
{
"address": "0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3",
"name": "DAI",
"symbol": "DAI",
"decimals": "18",
"network": "mainnet-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/dai.png"
},
{
"address": "0x03e85bfbb8e2a42b7bead9e88e9a1b19dbccf661471061807292120462396ec9",
"name": "DAI",
"symbol": "DAI",
"decimals": "18",
"network": "goerli-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/dai.png"
},
{
"address": "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
"name": "Wrapped BTC",
"symbol": "WBTC",
"decimals": "8",
"network": "mainnet-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/wbtc.png"
},
{
"address": "0x12d537dc323c439dc65c976fad242d5610d27cfb5f31689a0a319b8be7f3d56",
"name": "Wrapped BTC",
"symbol": "WBTC",
"decimals": "8",
"network": "goerli-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/wbtc.png"
},
{
"address": "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
"name": "USD Coin",
"symbol": "USDC",
"decimals": "6",
"network": "mainnet-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/usdc.png"
},
{
"address": "0x005a643907b9a4bc6a55e9069c4fd5fd1f5c79a22470690f75556c4736e34426",
"name": "USD Coin",
"symbol": "USDC",
"decimals": "6",
"network": "goerli-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/usdc.png"
},
{
"address": "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8",
"name": "Tether USD",
"symbol": "USDT",
"decimals": "6",
"network": "mainnet-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/usdt.png"
},
{
"address": "0x386e8d061177f19b3b485c20e31137e6f6bc497cc635ccdfcab96fadf5add6a",
"name": "Tether USD",
"symbol": "USDT",
"decimals": "6",
"network": "goerli-alpha",
"image": "https://dv3jj1unlp2jl.cloudfront.net/128/color/usdt.png"
}
]
17 changes: 17 additions & 0 deletions displayAccounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Account } from "./ui/pickAccounts";
import TOKENS from "./default-tokens.json";
import { omit } from "lodash";

export function display(accounts: Account[]) {
console.table(
accounts.map((a) => ({
...Object.fromEntries(
Object.entries(a.balances).map(([token, balance]) => {
const tokenInfo = TOKENS.find((t) => t.address === token);
return [tokenInfo?.name ?? token, balance] as const;
})
),
...omit(a, ["privateKey", "networkId", "balances"]),
}))
);
}
27 changes: 27 additions & 0 deletions formatTokenBalance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BigNumber, utils } from "ethers";

const formatTokenBalanceToCharLength =
(length: number) =>
(balance: string = "0", decimals = "18"): string => {
const balanceBn = BigNumber.from(balance);
const balanceFullString = utils.formatUnits(balanceBn, decimals);

// show max ${length} characters or what's needed to show everything before the decimal point
const balanceString = balanceFullString.slice(
0,
Math.max(length, balanceFullString.indexOf("."))
);

// make sure seperator is not the last character, if so remove it
// remove unnecessary 0s from the end, except for ".0"
let cleanedBalanceString = balanceString
.replace(/\.$/, "")
.replace(/0+$/, "");
if (cleanedBalanceString.endsWith(".")) {
cleanedBalanceString += "0";
}

return cleanedBalanceString;
};

export const formatTokenBalance = formatTokenBalanceToCharLength(9);
Loading

0 comments on commit a27adb5

Please sign in to comment.