Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
mainnet-pat committed Jun 24, 2021
0 parents commit c3b28b9
Show file tree
Hide file tree
Showing 22 changed files with 1,453 additions and 0 deletions.
Binary file added .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
hodl/__pycache__
node_modules
339 changes: 339 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions README.md
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)
Binary file added diamond.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions hodl.cash
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 added hodl.v1.0.0.zip
Binary file not shown.
5 changes: 5 additions & 0 deletions hodl/__init__.py
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']
14 changes: 14 additions & 0 deletions hodl/contract.py
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
116 changes: 116 additions & 0 deletions hodl/contract_finder.py
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]
Loading

0 comments on commit c3b28b9

Please sign in to comment.