-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #74 from shapeshift/hot-wallet-cli-part-1
- Loading branch information
Showing
17 changed files
with
4,711 additions
and
1 deletion.
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 |
---|---|---|
|
@@ -36,5 +36,6 @@ jobs: | |
id: test | ||
- name: Generate coverage report | ||
run: | | ||
forge clean | ||
forge coverage --report summary | ||
id: coverage |
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
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
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,66 @@ | ||
## Prerequisites | ||
|
||
- Install golang: https://go.dev/doc/install | ||
|
||
## Clone and Build | ||
|
||
```bash | ||
git clone https://gitlab.com/thorchain/thornode.git | ||
cd thornode/cmd/thornode | ||
go build --tags cgo,ledger | ||
``` | ||
|
||
## Create MultiSig | ||
|
||
- Add your key: | ||
```bash | ||
./thornode keys add {person1} --ledger | ||
``` | ||
- Export pubkey: | ||
```bash | ||
./thornode keys show {person1} --pubkey | ||
``` | ||
- Import signer pubkeys: | ||
```bash | ||
./thornode keys add {person2} --pubkey {pubkey} | ||
./thornode keys add {person3} --pubkey {pubkey} | ||
``` | ||
- Add multisig key: | ||
```bash | ||
./thornode keys add multisig --multisig {person1},{person2},{person3} --multisig-threshold 2 | ||
``` | ||
- Validate multisig address: | ||
```bash | ||
./thornode keys show multisig --address | ||
``` | ||
|
||
## Sign Transaction | ||
|
||
- Person 1 signs: | ||
```bash | ||
./thornode tx sign --from {person1} --multisig multisig {unsignedTx_epoch-N.json} --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --from ledger --ledger --sign-mode amino-json > signedTx_{person1}.json | ||
``` | ||
- Person 2 signs: | ||
```bash | ||
./thornode tx sign --from {person2} --multisig multisig {unsignedTx_epoch-N.json} --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --from ledger --ledger --sign-mode amino-json > signedTx_{person2}.json | ||
``` | ||
- Multisign: | ||
```bash | ||
./thornode tx multisign {unsignedTx_epoch-N.json} multisig signedTx_{person1}.json signedTx_{person2}.json --from multisig --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc > signedTx_multisig.json | ||
``` | ||
|
||
## Send Transaction | ||
|
||
- Simulate transaction: | ||
|
||
```bash | ||
./thornode tx broadcast signedTx_multisig.json --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --gas auto --dry-run > simulatedTx.json | ||
``` | ||
|
||
- Validate contents of `simulatedTx.json` for accuracy before broadcasting | ||
|
||
- Broadcast transaction: | ||
```bash | ||
./thornode tx broadcast signedTx_multisig.json --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --gas auto > tx.json | ||
``` | ||
- Copy the `txhash` value from `tx.json` to supply to the cli in order to continue |
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 @@ | ||
## Prerequisites | ||
|
||
- NodeJS (v18+): https://nodejs.org/en/download/package-manager | ||
- Yarn: https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable | ||
|
||
## Setup | ||
|
||
- Install dependencies: | ||
|
||
```bash | ||
yarn | ||
``` | ||
|
||
- Copy sample env file: | ||
|
||
```bash | ||
cp sample.env .env | ||
``` | ||
|
||
- Request environment variables and update `.env` with the appropriate values | ||
|
||
## Running | ||
|
||
- Run script: | ||
```bash | ||
yarn start | ||
``` |
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 @@ | ||
import os from 'node:os' | ||
import path from 'node:path' | ||
|
||
export const RFOX_DIR = path.join(os.homedir(), 'rfox') |
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,39 @@ | ||
import fs from 'node:fs' | ||
import { RFOX_DIR } from './constants' | ||
import { error, info, warn } from './logging' | ||
|
||
const deleteIfExists = (file: string) => { | ||
try { | ||
fs.accessSync(file, fs.constants.F_OK) | ||
fs.unlinkSync(file) | ||
} catch {} | ||
} | ||
|
||
export const write = (file: string, data: string) => { | ||
try { | ||
deleteIfExists(file) | ||
fs.writeFileSync(file, data, { mode: 0o400, encoding: 'utf8' }) | ||
} catch { | ||
error(`Failed to write file ${file}, exiting.`) | ||
warn('Manually save the contents at the specified file location if possible, or a temporary file for recovery!!!') | ||
info(data) | ||
process.exit(1) | ||
} | ||
} | ||
|
||
export const read = (file: string): string | undefined => { | ||
try { | ||
return fs.readFileSync(file, 'utf8') | ||
} catch {} | ||
} | ||
|
||
export const isEpochDistributionStarted = (epoch: number): boolean => { | ||
const regex = new RegExp(`epoch-${epoch}`) | ||
|
||
try { | ||
const files = fs.readdirSync(RFOX_DIR) | ||
return Boolean(files.filter(file => regex.test(file)).length) | ||
} catch { | ||
return false | ||
} | ||
} |
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,120 @@ | ||
import 'dotenv/config' | ||
import * as prompts from '@inquirer/prompts' | ||
import fs from 'node:fs' | ||
import path from 'node:path' | ||
import { Epoch } from '../types' | ||
import { RFOX_DIR } from './constants' | ||
import { isEpochDistributionStarted } from './file' | ||
import { IPFS } from './ipfs' | ||
import { error, info, success, warn } from './logging' | ||
import { create, recoverKeystore } from './mnemonic' | ||
import { Wallet } from './wallet' | ||
|
||
const run = async () => { | ||
const ipfs = await IPFS.new() | ||
|
||
const epoch = await ipfs.getEpoch() | ||
|
||
if (isEpochDistributionStarted(epoch.number)) { | ||
const cont = await prompts.confirm({ | ||
message: 'It looks like you have already started a distribution for this epoch. Do you want to continue? ', | ||
}) | ||
|
||
if (cont) return recover(epoch) | ||
|
||
info(`Please move or delete all existing files for epoch-${epoch.number} from ${RFOX_DIR} before re-running.`) | ||
warn('This action should never be taken unless you are absolutely sure you know what you are doing!!!') | ||
|
||
process.exit(0) | ||
} | ||
|
||
const mnemonic = await create(epoch.number) | ||
|
||
const confirmed = await prompts.confirm({ | ||
message: 'Have you securely backed up your mnemonic? ', | ||
}) | ||
|
||
if (!confirmed) { | ||
error('Unable to proceed knowing you have not securely backed up your mnemonic, exiting.') | ||
process.exit(1) | ||
} | ||
|
||
const wallet = await Wallet.new(mnemonic) | ||
|
||
await processEpoch(epoch, wallet, ipfs) | ||
} | ||
|
||
const recover = async (epoch?: Epoch) => { | ||
const ipfs = await IPFS.new() | ||
|
||
if (!epoch) epoch = await ipfs.getEpoch() | ||
|
||
const keystoreFile = path.join(RFOX_DIR, `keystore_epoch-${epoch.number}.txt`) | ||
const mnemonic = await recoverKeystore(keystoreFile) | ||
|
||
const wallet = await Wallet.new(mnemonic) | ||
|
||
await processEpoch(epoch, wallet, ipfs) | ||
} | ||
|
||
const processEpoch = async (epoch: Epoch, wallet: Wallet, ipfs: IPFS) => { | ||
await wallet.fund(epoch) | ||
const processedEpoch = await wallet.distribute(epoch) | ||
|
||
const processedEpochHash = await ipfs.addEpoch(processedEpoch) | ||
await ipfs.updateMetadata({ number: processedEpoch.number, hash: processedEpochHash }) | ||
|
||
success(`rFOX reward distribution for Epoch #${processedEpoch.number} has been completed!`) | ||
|
||
info( | ||
'Please update the rFOX Wiki (https://github.com/shapeshift/rFOX/wiki/rFOX-Metadata) and notify the DAO accordingly. Thanks!', | ||
) | ||
} | ||
|
||
const shutdown = () => { | ||
console.log() | ||
warn('Received shutdown signal, exiting.') | ||
process.exit(0) | ||
} | ||
|
||
const main = async () => { | ||
try { | ||
fs.mkdirSync(RFOX_DIR) | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
const fsError = err as NodeJS.ErrnoException | ||
if (fsError.code !== 'EEXIST') throw err | ||
} | ||
} | ||
|
||
const choice = await prompts.select<'run' | 'recover'>({ | ||
message: 'What do you want to do?', | ||
choices: [ | ||
{ | ||
name: 'Run rFox distribution', | ||
value: 'run', | ||
description: 'Start here to process a new rFox distribution epoch', | ||
}, | ||
{ | ||
name: 'Recover rFox distribution', | ||
value: 'recover', | ||
description: 'Use this to recover from an error during an rFox distribution epoch', | ||
}, | ||
], | ||
}) | ||
|
||
switch (choice) { | ||
case 'run': | ||
return run() | ||
case 'recover': | ||
return recover() | ||
default: | ||
error(`Invalid choice: ${choice}, exiting.`) | ||
process.exit(1) | ||
} | ||
} | ||
|
||
process.on('SIGINT', shutdown) | ||
process.on('SIGTERM', shutdown) | ||
|
||
main() |
Oops, something went wrong.