This repository has been archived by the owner on Mar 28, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 23
Deposit Submitter: Add tool for opening and funding deposits #38
Draft
Shadowfiend
wants to merge
10
commits into
main
Choose a base branch
from
deposit-submitter
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
296b629
Detail experimental Node flags in README
Shadowfiend 7dc2920
Nuke jsconfig.json
Shadowfiend 37ceecc
Improve some types across the project
Shadowfiend f88f024
Move bin/tbtc.js usage to top of file
Shadowfiend 1e04b34
Take minimum redemption fee from constants directly
Shadowfiend 4554d03
Implement proper transaction fee estimate
Shadowfiend b3fa342
Enable revert handling globally in web3
Shadowfiend e79d85a
Add tbtc-submitter script
Shadowfiend 1fe0f46
Point to Ropsten
Shadowfiend 9b37a9d
Bump submitter funding to fund for 3 hours
Shadowfiend File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,237 @@ | ||
#!/usr/bin/env node --experimental-modules | ||
// @ts-check | ||
const description = ` | ||
tbtc-submitter.js <ethereum-key-file> <bitcoin-wallet-file> | ||
In default mode, creates a single tBTC deposit with the given Ethereum | ||
key file and funds it using the Bitcoin wallet defined by the given | ||
file. | ||
|
||
-c,--continuous | ||
This flag repeatedly creates tBTC deposits and funds them. Rather | ||
than waiting for each deposit's funding transaction to fully | ||
confirm, a new deposit opened once the previous deposit's signing | ||
group becomes available. | ||
` | ||
|
||
import Web3 from "web3" | ||
import TBTC from "../index.js" | ||
import UI from "./ui.js" | ||
|
||
import BCoin from "bcoin" | ||
const { | ||
WalletDB, | ||
Script, | ||
MTX, | ||
Coin, | ||
} = BCoin.set('testnet') | ||
|
||
import ProviderEngine from "web3-provider-engine" | ||
import WebsocketSubprovider from "web3-provider-engine/subproviders/websocket.js" | ||
import Subproviders from "@0x/subproviders" | ||
import BitcoinHelpers from "../src/BitcoinHelpers.js"; | ||
import { DepositFactory } from "../src/Deposit.js" | ||
|
||
/** @typedef { import("../src/BitcoinHelpers.js").FoundTransaction } FoundTransaction */ | ||
/** | ||
* @typedef {Object} TransactionInfo | ||
* @prop {any} [transaction] | ||
*/ | ||
/** | ||
* @typedef {TransactionInfo & FoundTransaction} FundingTransactionInfo | ||
*/ | ||
|
||
const engine = new ProviderEngine({ pollingInterval: 1000 }) | ||
engine.addProvider( | ||
// For address 0x420ae5d973e58bc39822d9457bf8a02f127ed473. | ||
new Subproviders.PrivateKeyWalletSubprovider( | ||
"b6252e08d7a11ab15a4181774fdd58689b9892fe9fb07ab4f026df9791966990", | ||
), | ||
) | ||
engine.addProvider( | ||
new WebsocketSubprovider( | ||
{ | ||
rpcUrl: "wss://ropsten.infura.io/ws/v3/59fb36a36fa4474b890c13dd30038be5", | ||
debug: false, | ||
origin: '', | ||
}, | ||
// "http://eth-tx-node.default.svc.cluster.local:8545/", | ||
) | ||
) | ||
|
||
// -------------------------------- SETUP -------------------------------------- | ||
// @ts-ignore Web3 is declared as taking a `provider`, engine is a `Provider`. | ||
const web3 = new Web3(engine) | ||
engine.start() | ||
|
||
// --------------------------------- ARGS -------------------------------------- | ||
let args = process.argv.slice(2) | ||
if (process.argv[0].includes("tbtc-submitter.js")) { | ||
args = process.argv.slice(1) // invoked directly, no node | ||
} | ||
|
||
const ethereumKeyFile = args[0] | ||
const bitcoinWalletFile = args[1] | ||
|
||
let remainingPromise = Promise.resolve(true) | ||
/** @type {FundingTransactionInfo} */ | ||
let latestFundingInfo | ||
|
||
/** | ||
* @param {DepositFactory} Deposit Deposit factory object. | ||
* @param {import("../src/Deposit.js").BN} lotSize The size of the deposit lot | ||
* to create. | ||
* @param { import("bcoin/lib/wallet/wallet.js") } wallet Bitcoin wallet for | ||
* funding the deposit. | ||
* @param {number} maxFee The highest fee to use for the Bitcoin funding | ||
* transaction. | ||
* | ||
* @returns {()=>void} A function that, when called, will create a deposit of | ||
* the passed lotSize using the passed deposit factory and fund it | ||
* using the passed wallet. | ||
*/ | ||
function fundDepositAndCreationFn(Deposit, lotSize, wallet, maxFee) { | ||
const runner = async () => { | ||
const deposit = await Deposit.withAddress("0x7C935d413A35c28C9e7b91b82c8B5bfDA57E4780") // withSatoshiLotSize(lotSize) | ||
|
||
deposit | ||
.onBitcoinAddressAvailable(async address => { | ||
const receiveAddress = await wallet.receiveAddress() | ||
const keyRing = await wallet.getPrivateKey(receiveAddress, "") | ||
|
||
const lastTx = MTX.fromRaw(latestFundingInfo.transaction.hex, 'hex') | ||
const outputIndex = | ||
lastTx.outputs.findIndex( | ||
(_) => { | ||
return _.getAddress().toString(Deposit.config.bitcoinNetwork) == | ||
receiveAddress.toString(Deposit.config.bitcoinNetwork) | ||
} | ||
) | ||
|
||
const fundingTransaction = new MTX() | ||
fundingTransaction.addOutput({ | ||
script: Script.fromAddress(address), | ||
value: lotSize.toNumber(), | ||
}) | ||
let feePerKb | ||
try { | ||
feePerKb = await BitcoinHelpers.Transaction.estimateFeePerKb() | ||
} catch(_) { | ||
// Leave feePerKb null if we couldn't estimate. | ||
} | ||
await fundingTransaction.fund( | ||
[Coin.fromTX(lastTx, outputIndex, -1)], | ||
{ | ||
rate: feePerKb, | ||
maxFee, | ||
changeAddress: receiveAddress, | ||
} | ||
) | ||
fundingTransaction.sign(keyRing) | ||
|
||
latestFundingInfo = await BitcoinHelpers.Transaction.broadcast( | ||
fundingTransaction.toRaw().toString('hex') | ||
) | ||
console.log("Got transaction id", latestFundingInfo.transactionID) | ||
|
||
latestFundingInfo.transaction = | ||
await BitcoinHelpers.withElectrumClient(async electrumClient => { | ||
return electrumClient.getTransaction(latestFundingInfo.transactionID) | ||
}) | ||
console.log("Broadcast transaction", latestFundingInfo.transactionID) | ||
}) | ||
|
||
deposit.autoSubmit() | ||
return deposit.activeStatePromise | ||
} | ||
|
||
return () => { | ||
const nextPromise = runner() | ||
remainingPromise = remainingPromise.then(() => nextPromise) | ||
} | ||
} | ||
|
||
async function doTheThing() { | ||
web3.eth.defaultAccount = (await web3.eth.getAccounts())[0] | ||
const tbtc = await TBTC.withConfig({ | ||
web3: web3, | ||
bitcoinNetwork: "testnet", | ||
electrum: { | ||
testnet: { | ||
server: "electrumx-server.test.tbtc.network", | ||
port: 50002, | ||
protocol: "ssl" | ||
}, | ||
testnetPublic: { | ||
server: "testnet1.bauerj.eu", | ||
port: 50002, | ||
protocol: "ssl" | ||
}, | ||
testnetWS: { | ||
server: "electrumx-server.test.tbtc.network", | ||
port: 8443, | ||
protocol: "wss" | ||
} | ||
} | ||
}) | ||
|
||
const lotSizes = await tbtc.Deposit.availableSatoshiLotSizes() | ||
const lowestLotSize = lotSizes.sort((a, b) => a.sub(b).toNumber())[0] | ||
const maxFee = Math.max( | ||
await BitcoinHelpers.withElectrumClient(async client => { | ||
return (await client.getMinimumRelayFee()) * 10**8 | ||
}), | ||
parseInt(await tbtc.Deposit.constantsContract.methods.getMinimumRedemptionFee().call()), | ||
) * 2 | ||
console.log( | ||
`Opening deposits with ${lowestLotSize.toNumber()} sats once every 2 minutes...` | ||
) | ||
|
||
const wdb = new WalletDB({ db: 'memory' }); | ||
await wdb.open(); | ||
wdb | ||
const wallet = await wdb.create({ | ||
master: 'tprv8ZgxMBicQKsPfPae8Tt79fHXewcQqvEiCPyTUAPRtXYznzULBUtCYapXjcVvtWRz7fPsWUPz3bdZE3GWcbJoPifnUoKvSh8XK9g7pUdGraW', | ||
}) | ||
const addr = await wallet.receiveAddress(); | ||
// Actually let's check its existing balance eh? | ||
|
||
// Fund enough for 3 hours. | ||
const fundAmount = (lowestLotSize.toNumber() + maxFee) * 90 | ||
console.log(`Please fund address with ${fundAmount} sats: ${addr.toString()}`) | ||
console.log("Waiting...") | ||
latestFundingInfo = await BitcoinHelpers.Transaction.findOrWaitFor( | ||
addr.toString(), | ||
fundAmount, | ||
) | ||
latestFundingInfo.transaction = | ||
await BitcoinHelpers.Transaction.get(latestFundingInfo.transactionID) | ||
console.log(`Found transaction ${latestFundingInfo.transactionID}, proceeding optimistically...`) | ||
|
||
const createAndFundDeposit = fundDepositAndCreationFn( | ||
tbtc.Deposit, | ||
lowestLotSize, | ||
wallet, | ||
maxFee, | ||
) | ||
createAndFundDeposit() | ||
// Then run every 2 minutes. | ||
const interval = setInterval( | ||
createAndFundDeposit, | ||
2 /* minutes */ * 60 /* seconds */ * 1000 /* ms */ | ||
) | ||
|
||
await UI.promptQuestion("Type 'quit' and enter to stop: ", ["quit"]) | ||
|
||
console.log('Stopping new deposits...') | ||
clearInterval(interval) | ||
console.log('Waiting for existing deposits to finish funding...') | ||
await remainingPromise | ||
} | ||
|
||
doTheThing().then(a => { | ||
console.log(a) | ||
process.exit(0) | ||
}).catch(e => { | ||
console.log(e) | ||
process.exit(1) | ||
}) |
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,41 @@ | ||
import readline from "readline" | ||
|
||
const prompt = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
}) | ||
|
||
// Prompts for the question, waiting for valid answers or prefix thereof. | ||
// Case insensitive to the entered answer, returns the selected answer from the | ||
// valid list (not the user's input). If an invalid response is entered, | ||
// presents an error and reprompts. | ||
export default { | ||
promptQuestion: async function(question, validAnswers) { | ||
return this.promptValidInput( | ||
question, | ||
(found) => validAnswers.find(_ => _.toLowerCase().startsWith(found.toLowerCase())), | ||
"Invalid answer, try one of: [" + validAnswers.join("/") + "].", | ||
) | ||
}, | ||
|
||
promptValidInput: async function(question, inputCheckFn, retryText) { | ||
const answer = await this.promptInput(question) | ||
const validAnswer = inputCheckFn(answer) | ||
|
||
if (answer && validAnswer) { | ||
return (typeof validAnswer == "boolean" ? answer : validAnswer) | ||
} else { | ||
const retry = retryText || "Invalid answer, try again.".red | ||
console.log(retry) | ||
return this.promptValidInput(question, inputCheckFn, retryText) | ||
} | ||
}, | ||
|
||
promptInput: function(question) { | ||
return new Promise((resolve) => { | ||
prompt.question(question + " ", (answer) => { | ||
resolve(answer) | ||
}) | ||
}) | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Worth flagging, current code doesn't pay attention to a
-c
flag, and though it reads the two file parameters, it ignores them.