-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit a27adb5
Showing
30 changed files
with
3,932 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
node_modules | ||
dist | ||
yarn-error.log | ||
bin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]), | ||
})) | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
Oops, something went wrong.