-
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.
- Loading branch information
0 parents
commit c3b28b9
Showing
22 changed files
with
1,453 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
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,2 @@ | ||
hodl/__pycache__ | ||
node_modules |
Large diffs are not rendered by default.
Oops, something went wrong.
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,86 @@ | ||
# HODL - smart contract plugin for Electron Cash to timelock the funds | ||
![Logo](/pictures/logo.png) | ||
|
||
Did you ever regret acting under FUD or FOMO influence? | ||
|
||
Did you make big financial mistakes because of that? | ||
|
||
Do you want to protect yourself from such compulsory actions? | ||
|
||
Then you'll certainly want to use the HODL plugin for Electron Cash. With it you can lock your funds in a smart contract utilizing CLTV opcode, rendering them unspendable until a certain date or blockchain height. | ||
|
||
## Quick start | ||
|
||
1. First, download and verify sha256 of the hodl-vVERSION.zip file from [releases](https://github.com/mainnet-pat/hodl_ec_plugin/releases). Then go to your Electron Cash and install the plugin, | ||
2. The plugin tab should show up. Click "Create new HODL contract", choose the amount of BCH you want to lock until either certain blockchain height or certain date. | ||
3. Select the matured contract and click "Spend contract" to release the funds and claim them back to your wallet. | ||
|
||
## Limitations and notices | ||
|
||
1. The plugin is designed only for the standard HD wallets (multiaddress). Watchonly, Multisig wallets or wallets with imported private keys are not supported. | ||
|
||
2. If you lock the funds until certain date, allow for extra time (up to 70 minutes) after the contract maturation. This is due to the median time-past for the lock-time calculations (BIP-113). | ||
|
||
3. HODL contract can safely lock values greater than 21 BCH. | ||
|
||
## Contract details | ||
|
||
### Smart contract basics | ||
The contract is defined by a special address that is cryptographically determined by the contract itself. [Learn more](https://en.bitcoin.it/wiki/Pay_to_script_hash). Funds are "in the contract" when they are sent to this special address. | ||
|
||
A contract consists of challenges - requirements that have to be met to access the funds stored in it. This contract has only one challenge, but several prerequisites to satisfy. | ||
|
||
### HODL contract | ||
HODL contract is a simple upgrade over the most common pay to public key hash (P2PKH) address type. It utilizes in addition the OP_CHECKLOCKTIMEVERIFY opcode to check against the current blockchain height or current date before locking the funds to the redeemer's public key hash. The target locktime is provided to the contract upon its construction as an argument. | ||
|
||
Equivalent contract code in the high-level [cashscript](https://github.com/Bitcoin-com/cashscript) language: | ||
``` | ||
pragma cashscript ^0.6.0; | ||
contract hodl( | ||
int locktime, | ||
bytes20 pubkeyHash | ||
) { | ||
function spend(pubkey ownerPubkey, sig ownerSig) { | ||
require(tx.time >= locktime); | ||
require(hash160(ownerPubkey) == pubkeyHash); | ||
require(checkSig(ownerSig, ownerPubkey)); | ||
} | ||
} | ||
``` | ||
|
||
The actual script is implemented as follows: | ||
``` | ||
scriptSig: <sig> <pubkey> | ||
scriptPubkey: <locktime> OP_CHECKLOCKTIMEVERIFY OP_DROP OP_DUP OP_HASH160 <pubkeyhash> OP_EQUALVERIFY OP_CHECKSIG | ||
``` | ||
|
||
Transaction fees are very low and target 1sat/byte. However, they depend upon the number of inputs, as usual. | ||
|
||
## Disclaimer | ||
|
||
The author of this software is not a party to any HODL contract created, have no control over it and cannot influence it's outcome. The author is not responsible for legal implications of the HODL contract nor is competent to settle any disputes. The author is not responsible for the contract expected behavior. | ||
|
||
## License | ||
|
||
This software is distributed on GPL v2 license. The author encourage you to build your own smart contract plugins based on this plugin code, implement desired functions and submit a pull request to this repository or fork this project and compete, if I fail to deliver what you expect. Just remember to publish your improvements on the same license. | ||
|
||
## Contact the author | ||
|
||
With any problems contact me on telegram: **@mainnet_pat**, reddit: **u/mainnet_pat** | ||
|
||
## Donations | ||
|
||
If you wish to support development of the plugin, consider donating to: | ||
|
||
Cash Account: pat#111222 | ||
|
||
bitcoincash:qqsxjha225lmnuedy6hzlgpwqn0fd77dfq73p60wwp | ||
|
||
![donate](/pictures/donate.png) | ||
|
||
## Attributions | ||
|
||
This work is based on the codebase of [Mecenas Electron Cash plugin](https://github.com/KarolTrzeszczkowski/Mecenas-recurring-payment-EC-plugin.git) by licho. | ||
|
||
The diamond logo is taken from [https://icons8.com](https://icons8.com) |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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 @@ | ||
pragma cashscript ^0.6.0; | ||
|
||
contract hodl( | ||
int locktime, | ||
bytes20 pubkeyHash | ||
) { | ||
function spend(pubkey ownerPubkey, sig ownerSig) { | ||
require(tx.time >= locktime); | ||
require(hash160(ownerPubkey) == pubkeyHash); | ||
require(checkSig(ownerSig, ownerPubkey)); | ||
} | ||
} |
Binary file not shown.
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,5 @@ | ||
from electroncash.i18n import _ | ||
|
||
fullname = 'HODL' | ||
description = _('Lock your funds and have no FUD') | ||
available_for = ['qt'] |
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,14 @@ | ||
|
||
|
||
class Contract: | ||
def __init__(self, addresses, initial_tx=None, v=0): | ||
self.version = v | ||
self.initial_tx = initial_tx | ||
self.addresses = addresses | ||
|
||
@staticmethod | ||
def participants(version): | ||
pass | ||
@staticmethod | ||
def roles(version): | ||
pass |
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,116 @@ | ||
import queue | ||
from .hodl_contract import HodlContract | ||
from electroncash import util | ||
from electroncash.address import Address, ScriptOutput | ||
from itertools import permutations, combinations | ||
|
||
def synchronous_get_many(network, requests): | ||
""" modified synchrronous_get to handle batched requests """ | ||
responses = [] | ||
|
||
q = queue.Queue() | ||
network.send(requests, q.put) | ||
try: | ||
for _ in range(0, len(requests)): | ||
response = q.get(True, 30) | ||
if response.get('error'): | ||
raise util.ServerError(response.get('error')) | ||
responses.append(response) | ||
except queue.Empty: | ||
raise util.TimeoutException('Server did not answer') | ||
|
||
return responses | ||
|
||
def find_contract_in_wallet(wallet, contract_cls: HodlContract): | ||
contract_tuple_list=[] | ||
requests = [] | ||
contract_map = {} | ||
|
||
for hash, t in wallet.transactions.items(): | ||
contract = scan_transaction(t, contract_cls) | ||
if contract is None: | ||
continue | ||
|
||
contract_map[contract.address.to_scripthash_hex()] = contract | ||
requests.append(("blockchain.scripthash.listunspent", [contract.address.to_scripthash_hex()])) | ||
|
||
try: | ||
responses = synchronous_get_many(wallet.network, requests) | ||
except Exception as e: | ||
print(e) | ||
responses = [] | ||
|
||
for response in responses: | ||
if unfunded_contract(response['result']): # skip unfunded and ended contracts | ||
continue | ||
|
||
contract = contract_map[response['params'][0]] | ||
|
||
a=contract.addresses | ||
# print("hello there", contract.address.to_ui_string()) | ||
contract_tuple_list.append((response['result'], contract, find_my_role(a, wallet))) | ||
|
||
remove_duplicates(contract_tuple_list) | ||
return contract_tuple_list | ||
|
||
def remove_duplicates(contracts): | ||
c = contracts | ||
for c1, c2 in combinations(contracts,2): | ||
if c1[1].address == c2[1].address: | ||
c.remove(c1) | ||
return c | ||
|
||
|
||
def unfunded_contract(r): | ||
"""Checks if the contract is funded""" | ||
s = False | ||
if len(r) == 0: | ||
s = True | ||
for t in r: | ||
if t.get('value') == 0: # when contract was drained it's still in utxo | ||
s = True | ||
return s | ||
|
||
|
||
def scan_transaction(tx, contract_cls: HodlContract): | ||
out = tx.outputs() | ||
address, v, data = parse_p2sh_notification(out, contract_cls) | ||
if address is None or v is None or data is None: | ||
return | ||
no_participants = contract_cls.participants(v) | ||
if no_participants > (len(out)+1): | ||
return None | ||
candidates = get_candidates(out[1:], no_participants) | ||
for c in candidates: | ||
contract = contract_cls(c,tx.as_dict(),v=v, data=data) | ||
if contract.address.to_ui_string() == address: | ||
return contract | ||
|
||
|
||
def parse_p2sh_notification(outputs, contract_cls: HodlContract): | ||
opreturn = outputs[0] | ||
try: | ||
assert isinstance(opreturn[1], ScriptOutput) | ||
assert opreturn[1].to_ui_string().split(",")[1] == f" (4) '{contract_cls.op_return_signature()}'" | ||
a = opreturn[1].to_ui_string().split("'")[3][:42] | ||
version = float(opreturn[1].to_ui_string().split("'")[3][42:]) | ||
data = [int(e) for e in opreturn[1].to_ui_string().split("'")[5].split(' ')] | ||
return Address.from_string(a).to_ui_string(), version, data | ||
except: | ||
return None, None, None | ||
|
||
|
||
def get_candidates(outputs, participants): | ||
"""Creates all permutations of addresses that are not p2sh type""" | ||
candidates = [] | ||
for o in permutations(outputs, participants): | ||
kinds = [i[1].kind for i in o] | ||
if 1 in kinds: | ||
continue | ||
addresses = [i[1] for i in o] | ||
candidates.append(addresses) | ||
return candidates | ||
|
||
|
||
def find_my_role(candidates, wallet): | ||
return [0] |
Oops, something went wrong.