

Let’s Create Our Own Cryptocurrency | cranklin.com
source link: https://cranklin.wordpress.com/2017/07/11/lets-create-our-own-cryptocurrency/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

I’ve been itching to build my own cryptocurrency… and I shall give it an unoriginal & narcissistic name: Cranky Coin.
After giving it a lot of thought, I decided to use Python. GIL thread concurrency is sufficient. Mining might suffer, but can be replaced with a C mining module. Most importantly, code will be easier to read for open source contributors and will be heavily unit tested. Using frozen pip dependencies, virtualenv, and vagrant or docker, we can fire this up fairly easily under any operating system.

I decided to make Cranky coin a VERY simple, but complete cryptocurrency/blockchain/wallet system. This implementation will not include smart contracts, transaction rewards, nor utilize Merkel trees. Its only purpose is to act as a decentralized ledger. It is rudimentary, but I will eventually fork a few experimental blockchains with advanced features from this one.
The Wallet
This currency will only be compatible with ECC secp256k1 curve key pairs.
We need to provide a mechanism for users to:
– generate key pairs
– get the balance
– create transactions
– sign transactions
– calculate the transaction hash
– broadcast the transaction to the nodes
In order to generate the key pairs, we utilize the pyelliptic library and create a pyelliptic.ECC instance. This will generate a key pair for you. You can also generate an ECC instance with a known key pair.
def
__init__(
self
, private_key
=
None
, public_key
=
None
):
if
private_key
is
not
None
and
public_key
is
not
None
:
self
.__private_key__
=
private_key.decode(
'hex'
)
self
.__public_key__
=
public_key.decode(
'hex'
)
self
.ecc
=
self
.generate_ecc_instance()
def
generate_ecc_instance(
self
):
if
self
.__private_key__
is
None
or
self
.__public_key__
is
None
:
print
"ECC keys not provided. Generating ECC keys"
ecc
=
pyelliptic.ECC(curve
=
'secp256k1'
)
self
.__private_key__
=
ecc.get_privkey()
self
.__public_key__
=
ecc.get_pubkey()
else
:
ecc
=
pyelliptic.ECC(curve
=
'secp256k1'
, privkey
=
self
.__private_key__, pubkey
=
self
.__public_key__)
return
ecc
def
get_pubkey(
self
,
hex
=
True
):
return
self
.ecc.get_pubkey().encode(
'hex'
)
if
hex
else
self
.ecc.get_pubkey()
def
get_privkey(
self
,
hex
=
True
):
return
self
.ecc.get_privkey().encode(
'hex'
)
if
hex
else
self
.ecc.get_privkey()
The main purpose for our private key is to sign transactions originating from our public key. This way, the nodes can validate transactions with the public key. In other words,signature = sign(priv_key, message)
andverified = verify(signature, message, pub_key)
or if you prefer,verify(sign(priv_key, message), message, pub_key) = true
Implemented:
def
sign(
self
, message):
return
self
.ecc.sign(message).encode(
'hex'
)
def
verify(
self
, signature, message, public_key
=
None
):
if
public_key
is
not
None
:
return
pyelliptic.ECC(curve
=
'secp256k1'
, pubkey
=
public_key.decode(
'hex'
)).verify(signature.decode(
'hex'
), message)
return
self
.ecc.verify(signature, message)
In order to check your balance, we’d need to query any full node that has a copy of the entire blockchain. There are better ways of implementing this, but I’ve hacked something together which iterates over each transaction in each block on the chain to search for your public key:
def
get_balance(
self
, address):
balance
=
0
for
block
in
self
.blocks:
for
transaction
in
block.transactions:
if
transaction[
"from"
]
=
=
address:
balance
-
=
transaction[
"amount"
]
if
transaction[
"to"
]
=
=
address:
balance
+
=
transaction[
"amount"
]
return
balance
In order to spend the coins that belong to our public address, we need to be able to create a transaction, hash it, and sign it with our private key:
def
create_transaction(
self
, to, amount):
timestamp
=
datetime.datetime.utcnow().isoformat()
signature
=
self
.sign(
self
.generate_signable_transaction(
self
.get_pubkey(),
to,
amount,
timestamp))
transaction
=
{
"from"
:
self
.get_pubkey(),
"to"
: to,
"amount"
: amount,
"signature"
: signature,
"timestamp"
: timestamp,
"hash"
: transaction_hash
}
transaction[
"hash"
]
=
self
.calculate_transaction_hash(transaction)
return
self
.broadcast_transaction(transaction)
def
calculate_transaction_hash(
self
, transaction):
"""
Calculates sha-256 hash of transaction
:param transaction: transaction
:type transaction: dict(from, to, amount, timestamp, signature, (hash))
:return: sha256 hash
:rtype: str
"""
# pop hash so method can calculate transactions pre or post hash
data
=
transaction.copy()
data.pop(
"hash"
,
None
)
data_json
=
json.dumps(data, sort_keys
=
True
)
hash_object
=
hashlib.sha256(data_json)
return
hash_object.hexdigest()
def
generate_signable_transaction(
self
, from_address, to_address, amount, timestamp):
return
":"
.join((from_address, to_address, amount, timestamp))
Now that our transaction is ready, broadcasting it is fairly simple. We first retrieve the latest list of full nodes and then POST the transaction to each node:
def
broadcast_transaction(
self
, transaction):
self
.request_nodes_from_all()
bad_nodes
=
set
()
data
=
{
"transaction"
: transaction
}
for
node
in
self
.full_nodes:
url
=
TRANSACTIONS_URL.
format
(node, FULL_NODE_PORT)
try
:
response
=
requests.post(url, data)
except
requests.exceptions.RequestException as re:
bad_nodes.add(node)
for
node
in
bad_nodes:
self
.remove_node(node)
bad_nodes.clear()
return
Note: Here, I am making requests serially. This is not good and will be slow as the list of nodes grow. It’s very simple to replace “requests” with “grequests” (gevent wrapped requests) which can fire off concurrently and return a list of response objects. By the time you read this post, it may already be changed in the main repo. It’s lame because my explanation took more time than changing a few lines of code.
The Transaction
One may argue that the “transaction” is the smallest unit of a blockchain. Transactions make up the core data on each block.
Our transactions look something like this:
transaction
=
{
"from"
: source_public_key,
"to"
: destination_public_key,
"amount"
: amount,
"signature"
: signature,
"timestamp"
: timestamp,
"hash"
: transaction_hash
}
When a transaction is verified, the nodes must verify the following:
– source address has enough funds to send the listed amount to the destination address (this is done by looking through the transaction history of the source_public_key)
– signature of the transaction is validated with the public key. (this guarantees that the transaction was created by the owner of the private key)
– the hash of the transaction is correct
– the hash of the transaction is not found anywhere else on the blockchain (prevent a double spend attack)
I’ve reserved the final transaction in each list of transactions for the reward to the miner. This transaction is verified separately…
The Block
When the full node has a list of unconfirmed transactions, it is ready to mine a block. We pop off transactions and validate each one before adding it to our block. When we have enough transactions, we add our reward transaction which must apply the following algorithm:
def
get_reward(
self
, index):
# 50 coins per block. Halves every 1000 blocks
reward
=
self
.INITIAL_COINS_PER_BLOCK
for
i
in
range
(
1
, ((index
/
self
.HALVING_FREQUENCY)
+
1
)):
reward
=
reward
/
2
return
reward
Initially, the nodes reward 50 coins per block to the miner. Every 1000 blocks mined, this reward is cut in half. Eventually, the number of coins rewarded turns to 0. This is how coins like Bitcoin can guarantee a maximum limit of coins in circulation. In our case, after 6000 blocks have been mined, there will be no more coins released. I haven’t implemented transaction fees into Cranky coin yet, but that is crucial for continued incentivization for mining and keeping the blockchain alive.
Remember: Trying to cheat this will cause our block to get rejected from the other nodes… and our blocks are afraid of rejection.
The block is just a data object. (Not really sure if Pythonistas their own term for POJOs :D)
class
Block(
object
):
def
__init__(
self
, index, transactions, previous_hash, current_hash, timestamp, nonce):
"""
:param index: index # of block
:type index: int
:param transactions: list of transactions
:type transactions: list of transaction dicts
:param previous_hash: previous block hash
:type previous_hash: str
:param current_hash: current block hash
:type current_hash: str
:param timestamp: timestamp of block mined
:type timestamp: int
:param nonce: nonce
:type nonce: int
transaction
:type transaction: dict(from, to, amount, timestamp, signature, hash)
:type from: string
:type to: string
:type amount: float
:type timestamp: int
:type signature: string
:type hash: string
"""
self
.index
=
index
self
.transactions
=
transactions
self
.previous_hash
=
previous_hash
self
.current_hash
=
current_hash
self
.timestamp
=
timestamp
self
.nonce
=
nonce
def
__repr__(
self
):
return
"<Crankycoin Block {}>"
.
format
(
self
.index)
def
__str__(
self
):
return
str
(
self
.__dict__)
def
__eq__(
self
, other):
return
self
.__dict__
=
=
other.__dict__
def
__ne__(
self
, other):
return
not
self
=
=
other
Note that it contains a list of transactions, current hash, previous hash, and a nonce.
There is one special block, also known as the “genesis block”. This is the first block in the chain and it is hardcoded into the codebase.
def
get_genesis_block(
self
):
genesis_transaction
=
{
"from"
:
"0"
,
"to"
:
"0409eb9224f408ece7163f40a33274d99b6b3f60e41b447dd45fcc6371f57b88d9d3583c358b1ea8aea4422d17c57de1418554d3a1cd620ca4cb296357888ea596"
,
"amount"
:
50
,
"signature"
:
"0"
,
"timestamp"
:
0
,
"hash"
:
0
}
genesis_transactions
=
[genesis_transaction]
current_hash
=
self
.calculate_block_hash(
0
,
0
,
0
, genesis_transactions,
0
)
genesis_block
=
Block(
0
, genesis_transactions,
0
, current_hash,
0
,
0
);
return
genesis_block
The Blockchain
In Cranky Coin, the Blockchain is really just an instantiatable class that:
– holds the chain of blocks as a list (Nothing is persisted to disk in this primitive implementation)
– holds the list of unconfirmed transactions (treated like a FIFO queue)
– includes various methods for mining, validating, altering the chain, adding blocks, calculating hashes, and other blockchain tools
This provides the core of Cranky coin’s functionality.
def
validate_block(
self
, block):
# verify genesis block integrity
# TODO implement and use Merkle tree
if
block.index
=
=
0
:
if
block !
=
self
.get_genesis_block():
raise
GenesisBlockMismatch(block.index,
"Genesis Block Mismatch: {}"
.
format
(block))
return
True
# current hash of data is correct and hash satisfies pattern
if
not
self
._check_hash_and_hash_pattern(block):
raise
InvalidHash(block.index,
"Invalid Hash: {}"
.
format
(block.current_hash))
# block index is correct and previous hash is correct
if
not
self
._check_index_and_previous_hash(block):
raise
ChainContinuityError(block.index,
"Block not compatible with previous block id: {} and hash: {}"
.
format
(block.index
-
1
, block.previous_hash))
# block reward is correct based on block index and halving formula
if
not
self
._check_transactions_and_block_reward(block):
raise
InvalidTransactions(block.index,
"Transactions not valid. Insufficient funds and/or incorrect block reward"
)
return
True
In our block validation code, I’ve created and designated just a few different types of exceptions depending on the circumstances behind the validation error.
Our block hash is calculated based on the index, previous hash, timestamp, transactions, and nonce:
def
calculate_block_hash(
self
, index, previous_hash, timestamp, transactions, nonce
=
0
):
"""
Calculates sha-256 hash of block based on index, previous_hash, timestamp, transactions, and nonce
:param index: index of block to hash
:type index: int
:param previous_hash: previous block hash
:type previous_hash: str
:param timestamp: timestamp of block mined
:type timestamp: int
:param transactions: list of transactions
:type transactions: list of transaction dicts
:param nonce: nonce
:type nonce: int
:return: sha256 hash
:rtype: str
"""
data
=
{
"index"
: index,
"previous_hash"
: previous_hash,
"timestamp"
: timestamp,
"transactions"
: transactions,
"nonce"
: nonce
}
data_json
=
json.dumps(data, sort_keys
=
True
)
hash_object
=
hashlib.sha256(data_json)
return
hash_object.hexdigest()
Whenever a new block gets added to our blockchain or chain alteration is in order (we have forked from the main branch), we must go through a series of validations and lock the blocks as the list of blocks is not inherently thread-safe.
For this reason, I establish two locks (blocks and unconfirmed transactions) in the constructor of our blockchain class. I don’t want these to be susceptible to race conditions. I’ve initiate withself.blocks_lock = threading.Lock()
andself.unconfirmed_transactions_lock = threading.Lock()
Now, we can modify our blockchain in peace:
def
alter_chain(
self
, blocks):
#TODO enforce finality through key blocks
fork_start
=
blocks[
0
].index
alternate_blocks
=
self
.blocks[
0
:fork_start]
alternate_blocks.extend(blocks)
alternate_chain
=
Blockchain(alternate_blocks)
if
alternate_chain.get_size() >
self
.get_size():
with
self
.blocks_lock:
self
.blocks
=
alternate_blocks
return
True
return
False
def
add_block(
self
, block):
#TODO change this from memory to persistent
if
self
.validate_block(block):
with
self
.blocks_lock:
self
.blocks.append(block)
return
True
return
False
When we mine a block, we:
1) pop off transactions from the unconfirmed transactions pool
2) validate them
3) add a reward transaction
def
mine_block(
self
, reward_address):
#TODO add transaction fees
transactions
=
[]
latest_block
=
self
.get_latest_block()
new_block_id
=
latest_block.index
+
1
previous_hash
=
latest_block.current_hash
for
i
in
range
(
0
,
self
.MAX_TRANSACTIONS_PER_BLOCK):
unconfirmed_transaction
=
self
.pop_next_unconfirmed_transaction()
if
unconfirmed_transaction
is
None
:
break
if
unconfirmed_transaction[
"hash"
] !
=
self
.calculate_transaction_hash(unconfirmed_transaction):
continue
if
unconfirmed_transaction[
"hash"
]
in
[transaction[
"hash"
]
for
transaction
in
transactions]:
continue
if
self
.find_duplicate_transactions(unconfirmed_transaction[
"hash"
]):
continue
if
not
self
.verify_signature(
unconfirmed_transaction[
"signature"
],
":"
.join((
unconfirmed_transaction[
"from"
],
unconfirmed_transaction[
"to"
],
str
(unconfirmed_transaction[
"amount"
]),
str
(unconfirmed_transaction[
"timestamp"
]))),
unconfirmed_transaction[
"from"
]):
continue
transactions.append(unconfirmed_transaction)
if
len
(transactions) <
1
:
return
None
reward_transaction
=
{
"from"
:
"0"
,
"to"
: reward_address,
"amount"
:
self
.get_reward(new_block_id),
"signature"
:
"0"
,
"timestamp"
: datetime.datetime.utcnow().isoformat()
}
reward_transaction[
"hash"
]
=
self
.calculate_transaction_hash(reward_transaction)
transactions.append(reward_transaction)
Then we:
4) add these transactions to the new block
5) hash the block
6) check to see if the block hash starts with a prefix of “0000”
7) if the block hash doesn’t start with a prefix of “0000”, increase the nonce and go back to step 5.
8) if the block hash does start with a prefix of “0000”, broadcast the block to the other nodes and await confirmation (we are getting ahead of ourselves as interaction with other nodes occurs within our FullNode class)
timestamp
=
datetime.datetime.utcnow().isoformat()
def
new_hash(nonce):
return
self
.calculate_block_hash(new_block_id, previous_hash, timestamp, transactions, nonce)
i
=
0
while
new_hash(i)[:
4
] !
=
"0000"
:
latest_block
=
self
.get_latest_block()
if
latest_block.index >
=
new_block_id
or
latest_block.current_hash !
=
previous_hash:
# Next block in sequence was mined by another node. Stop mining current block.
# identify in-progress transactions that aren't included in the latest_block and place them back in
# the unconfirmed transactions pool
for
transaction
in
transactions[:
-
1
]:
if
transaction
not
in
latest_block.transactions:
self
.push_unconfirmed_transaction(transaction)
return
None
i
+
=
1
block
=
Block(new_block_id, transactions, previous_hash, new_hash(i), timestamp, i)
return
block
The Node
Our FullNode class is basically the controller to our blockchain. It is our external interface which wraps our blockchain code and is responsible for interacting with other nodes on the Cranky coin network. We shall use Klein as our lightweight Rest API server.
These are the URL constants defined in our node module:
FULL_NODE_PORT
=
"30013"
NODES_URL
=
"http://{}:{}/nodes"
TRANSACTIONS_URL
=
"http://{}:{}/transactions"
BLOCK_URL
=
"http://{}:{}/block/{}"
BLOCKS_RANGE_URL
=
"http://{}:{}/blocks/{}/{}"
BLOCKS_URL
=
"http://{}:{}/blocks"
TRANSACTION_HISTORY_URL
=
"http://{}:{}/address/{}/transactions"
BALANCE_URL
=
"http://{}:{}/address/{}/balance"
Furthermore, when we instantiate a full node, the mining loop automatically begins in the background before the API server fires up:
class
FullNode(NodeMixin):
NODE_TYPE
=
"full"
blockchain
=
None
app
=
Klein()
def
__init__(
self
, host, reward_address, block_path
=
None
):
self
.host
=
host
self
.request_nodes_from_all()
self
.reward_address
=
reward_address
self
.broadcast_node(host)
if
block_path
is
None
:
self
.blockchain
=
Blockchain()
else
:
self
.load_blockchain(block_path)
thread
=
threading.Thread(target
=
self
.mine, args
=
())
thread.daemon
=
True
thread.start()
print
"\n\nfull node server started...\n\n"
self
.app.run(
"localhost"
, FULL_NODE_PORT)
The mining method is a wrapper to the blockchain mine method except that
– it loops infinitely
– after a block is mined, it broadcasts it to the other nodes on the network for confirmation before it continues to mine
– if a block is not confirmed, the node re-syncs with the other nodes on the network and recycles the transactions back into the unconfirmed transactions pool
def
mine(
self
):
print
"\n\nmining started...\n\n"
while
True
:
latest_block
=
self
.blockchain.get_latest_block()
latest_hash
=
latest_block.current_hash
latest_index
=
latest_block.index
block
=
self
.blockchain.mine_block(
self
.reward_address)
if
not
block:
continue
statuses
=
self
.broadcast_block(block)
if
statuses[
'expirations'
] > statuses[
'confirmations'
]
or
\
statuses[
'invalidations'
] > statuses[
'confirmations'
]:
self
.synchronize()
new_latest_block
=
self
.blockchain.get_latest_block()
if
latest_hash !
=
new_latest_block.current_hash
or
\
latest_index !
=
new_latest_block.index:
#latest_block changed after sync.. don't add the block.
self
.blockchain.recycle_transactions(block)
continue
self
.blockchain.add_block(block)
When we broadcast a block, we are either expecting a confirmation, invalidation, or expiration.
A confirmation is our happy path scenario where every node mines blocks without any conflicts.
An invalidation occurs when a node reports that either your block or the transactions within are invalid. Ideally, validation checks should not fail as they are validated when mining the block. This usually means foul play or different versions of the node software are running.
An expiration occurs when your block index is behind another node’s. The longest chain (if valid) wins.
def
broadcast_block(
self
, block):
#TODO convert to grequests and concurrently gather a list of responses
statuses
=
{
"confirmations"
:
0
,
"invalidations"
:
0
,
"expirations"
:
0
}
self
.request_nodes_from_all()
bad_nodes
=
set
()
data
=
{
"block"
: block,
"host"
:
self
.host
}
for
node
in
self
.full_nodes:
url
=
BLOCKS_URL.
format
(node, FULL_NODE_PORT)
try
:
response
=
requests.post(url, data)
if
response.status_code
=
=
202
:
# confirmed and accepted by node
statuses[
"confirmations"
]
+
=
1
elif
response.status_code
=
=
406
:
# invalidated and rejected by node
statuses[
"invalidations"
]
+
=
1
elif
response.status_code
=
=
409
:
# expired and rejected by node
statuses[
"expirations"
]
+
=
1
except
requests.exceptions.RequestException as re:
bad_nodes.add(node)
for
node
in
bad_nodes:
self
.remove_node(node)
bad_nodes.clear()
return
statuses
Once again, before you criticize me for it, I am broadcasting to the other nodes serially. A simple change to use grequests corrects this.
It is also worth mentioning how my peer-to-peer/peer discovery system works. Rather than relying on the nodes to forward requests/broadcasts to all of the nodes, on cranky coin, every node and client is aware of every node… and thus responsible for broadcasting to everybody.
As long as we start off with at least one good node, we can retrieve a list of all the other nodes, calculate a union of these nodes, and populate or update our own list of nodes.
def
request_nodes(
self
, node, port):
url
=
NODES_URL.
format
(node, port)
try
:
response
=
requests.get(url)
if
response.status_code
=
=
200
:
all_nodes
=
response.json()
return
all_nodes
except
requests.exceptions.RequestException as re:
pass
return
None
def
request_nodes_from_all(
self
):
full_nodes
=
self
.full_nodes.copy()
bad_nodes
=
set
()
for
node
in
full_nodes:
all_nodes
=
self
.request_nodes(node, FULL_NODE_PORT)
if
all_nodes
is
not
None
:
full_nodes
=
full_nodes.union(all_nodes[
"full_nodes"
])
else
:
bad_nodes.add(node)
self
.full_nodes
=
full_nodes
for
node
in
bad_nodes:
self
.remove_node(node)
return
This mechanism is shared by the light client/wallet as well. Nobody on the network needs to be aware of clients/wallets. However, nodes need to be visible. So when a full node is started, it is also responsible for broadcasting itself to the network:
def
broadcast_node(
self
, host):
self
.request_nodes_from_all()
bad_nodes
=
set
()
data
=
{
"host"
: host
}
for
node
in
self
.full_nodes:
url
=
NODES_URL.
format
(node, FULL_NODE_PORT)
try
:
requests.post(url, data)
except
requests.exceptions.RequestException as re:
bad_nodes.add(node)
for
node
in
bad_nodes:
self
.remove_node(node)
bad_nodes.clear()
return
Here I’ve covered the significant bits and pieces of Cranky coin. The rest can be perused directly from the repository: https://github.com/cranklin/crankycoin
Keep an eye on the repository. I will update the docs with clear instructions on running the tests, running a node, opening a wallet, etc.
Let’s recap:
I’ve walked the reader through the creation of Cranky Coin. Cranky Coin is a primitive yet complete cryptocurrency / blockchain / wallet which relies on PoW (proof-of-work) mining. Similar to Bitcoin classic, all full nodes are mining nodes.
Improvements:
– hash rate should be accounted for and hash difficulty should increase rather than staying static at “0000”.
– merkel trees would speed up transaction validation
– grequests would speed up broadcast requests to all nodes
– persistence of blocks and unconfirmed transactions
– transactions can make room for additional data and/or smart contracts
– unconfirmed_transactions can use a better queueing mechanism vs bastardizing a list as a FIFO
– sharding
– larger blocks with more transactions
– https
– ditch “Proof of Work” for “Proof of ****” (will reveal later)
– better testing (the blockchain module was thoroughly unit tested but the node module was just me brainstorming in code)
– shorter public keys for IBAN compliance
– many cool ideas which I shall reveal later
In case you’re wondering, I am not attempting to ICO Cranky coin. I realize that most ICOs are total garbage or outright scams. This hurts the entire crypto community. Believe it or not, mosts ICOs are raising tens of millions of dollars with a blockchain codebase less mature and less ready than Cranky coin. Some are raising money with nothing more than a “promise” (or whitepaper) to implement a “better blockchain” in the future.
If I ever decide to ICO, I will introduce something with intrinsic value and quality. But at present, the purpose of Cranky coin is to educate and draw developers to the world of cryptocurrency.
BTC donation wallet: 1CrANKo4jXDaromxFJZFJb3dcbu8icFPmJ
ETH donation wallet: 0x649283Bd1eC11eCb51B349867f99c0bEAE96DCf8
Recommend
-
10
How to Create a Computer Virus November 29, 2011 I was sick (and bored) this week, and my brain didn’t want to do any work. I was laying in bed with a cold/flu virus...
-
3
How to Make a Simple Computer Virus with PythonA great way to test your skills in a computer language is to try making a computer virus with that language. Python seems to be the hot language right now… so let’s make a Python virus.
-
16
How we ended up writing our own programming language Oct 21, 2014 lokad
-
11
My Face Tracking RobotI’ve been playing with the OpenCV (Computer Vision) library for Python in my spare time, and it is pretty fun. It opens up a whole new world of possibilities. Now I can work on motion detection, facial recognition, sen...
-
17
README.md Nuclear-Facebook-Poke-Bot Facebook Poke Bot is easy to use and automatically "pokes" AND "po...
-
10
Ruby Magic Brewing our own Template Lexer in Ruby Benedikt Deicke on Jul 2, 2019 “I absolutely love AppSignal.” Discover AppS...
-
9
Scratching our own itch The team behind AppSignal has been active in the Ruby on Rails community since 2005. We were the second company in the Netherlands to use it in production. We started AppSignal in 2012, after the launch of Rai...
-
9
Stupid Simple Computer Virus in 3 Lines of Code April 19, 2011 Yes, it’s pretty useless and completely harmless. A DOS batch file virus in 3 simple lines. Useless…...
-
7
How to create your own cryptocurrency with JavaScriptWritten by Atharva Deosthale ✏️ Nowadays, there are a lot of methods to send money digitally to so...
-
5
Games should let us choose our own stakes / We don’t all have the luxury of sticking with a match until the end.By T.C. Sot...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK