Users are finding new ways to user the XEM everyday, in order to further support the sentiment the NEM Tipbot for Reddit has been created.
What is the NEM Tipbot for Reddit?
The NEM Tipbot for Reddit allows users to tip other users in XEM directly through Reddit. This is similar to existing crypto currency bots, however, we take advantage of features inherit to the NEM block-chain. In fact, this tip bot is the first trustless cryptocurrency tip bot.
Security
The NEM Tipbot uses 2-of-3 multi-sig to give users more security and explicit control over their tipping funds. What does this mean? In short, all tipping transactions made by the bot on behalf of another user must be cleared by that user. Furthermore, the user controls 2 out of the 3 cosigner accounts, which gives the user complete control over the account. If the bot’s database were to be compromised the attacker would not be able to steal XEM from any multi-sig accounts.
The only case where an account is not multi-sig is when a receiving user’s Reddit account has not been registered with the bot. i.e. Alice tips Bob on Reddit but Bob has not registered his account with the tip-bot yet. In this case, the bot automatically generates a new account for Bob and begins the multi-sig transaction on behalf of Alice. Once Alice signs the transaction via the Nano Wallet, XEM will be transfered from Alice’s multi-sig account to Bob’s account on the blockchain. At this point, however, Bob’s account is not a multi-sig account. Bob’s tip account will be converted to a multi-sig account once he registers with the bot.
Realistically though, most users will want to register promptly to claim their XEM.
How to use the bot
Registering is easy, simply send a message to /u/nemtipbot with a subject or body of register
.
You will receive a response from the bot similar to the one pictured below:
follow the instructions provided in the message.
Once the bot has received your transaction with the challenge code, it will generate a multi-sig account and send you an encrypted transaction containing the private key of the account. You’re not required to do anything with this transaction, but you are free to import the private key into Nano Wallet.
You will also receive a follow up private message on reddit confirming your transaction has been received and your account created. The reddit message will also include the address to your new account so that you may begin funding it immediately.
Tipping
To tip a user, simply respond to a comment of a user you want to tip using the !tipxem
command and an amount. For example, to send 100 XEM to a user simply respond with a comment beginning with !tipxem 100
. The bot will automatically transfer funds from your tip account to the other user’s tip account.
Example of tipping:
If the receiving user does not already have a tip account, the bot will automatically generate a wallet for that user. Once the receiving user registers with the bot, they will be able to access their funds.
In this way, the NEM Tipbot for Reddit differs from most other tipping bots as it does not utilize a shared hot wallet, instead each user has a seperate account in which they have complete control over. In the case of catastrophic data failure (bot’s database exploding) users will still have complete access to their funds due to multi-sig. Users can still use the two accounts they control to move their funds out of their account without using the tipping bot.
Writing the NEM Tipbot for Reddit
The NEM Tipbot for Reddit was built on node.js using the NEM-sdk which is available via
npm install nem-sdk
In addition to the NEM-sdk, we use Sequelize for storing our accounts in a SQL database (sqlite by default), and Snoowrap to monitor Reddit comments.
Full source-code for the NEM Tipbot is available on GitHub
The NEM Tipbot has a few responsibilities as outlined below
- Allow a Reddit user to register with bot
- Monitor PMs for the trigger phrase
register
in the body/subject
- Generate and store a challenge code associated to that reddit user.
- Reply to the PM providing instructions to the Reddit user.
- Monitor the NEM blockchain for transactions to said NEM account that contain the user’s challenge code and public address.
- Once said transaction is received and the challenge code is verified convert the NEM account to a multi-sig account using the sender’s public-key, the supplied public-key in the message, and the receiving account public-key.
- Allow user to tip other users on reddit
- Monitor reddit comments for the trigger phrase
!tipxem
in the body.
- If the receiving Reddit user has never been tipped, generate a NEM account on their behalf.
- Generate a multi-sig transfer transaction to the receiving user’s account and post it to the NEM network.
- Reply to the original Reddit comment notifying the receiving user of their tip.
I won’t detail every aspect of the code but instead highlight the more interesting pieces.
One of the most important pieces during the registration process is converting an account to a multi-sig account.
Here’s a snippet of code responsible for that process:
...
var common = nem.model.objects.create("common")(WALLET_PASSWORD, userCommon.privateKey);
var publicAddress = nem.model.address.toAddress(originPublicKey, NETWORK);
var kp = nem.crypto.keyPair.create(cosignerCommon.privateKey);
var multisigAggregateModificationTransaction =
{
isMultiSig: false,
modifications: [
{
'modificationType': 1,
'cosignatoryAccount': originPublicKey
},
{
'modificationType': 1,
'cosignatoryAccount': publicKey
},
{
'modificationType': 1,
'cosignatoryAccount': kp.publicKey.toString()
}
],
minCosignatories: {
"relativeChange": 2
},
relativeChange: 2
}
console.log(multisigAggregateModificationTransaction);
var prepareMultisigAggModTransaction = nem.model.transactions.prepare("multisigAggregateModificationTransaction");
var preparedMultisigAggModTransaction = prepareMultisigAggModTransaction(common, multisigAggregateModificationTransaction, NETWORK);
...
In order to convert an account to a multi-sig account we must create a multisig aggregated modification transaction, that’s a mouthful but essentially we create a special type of transaction that modifies an account and adds co-signatories. When we create the multisigAggregateModificationTransaction
object we add 3 co-signatories, the two that are controlled by the user, and the one that the bot controls. Pretty simple, then all we need to do is prepare the transaction and publish it to the network.
Another interesting piece of code to look at is the following function walletToWallet
which we use to generate a multi-sig transaction from one user to another
function walletToWallet(fromUserWallet, fromUserCosignerWallet, toUserWallet, amount) {
var fromUserAccount = getFirstAccount(fromUserWallet);
var fromUserCosignerAccount = getFirstAccount(fromUserCosignerWallet);
var toUserAccount = getFirstAccount(toUserWallet);
// Multi-sig account
var fromUserDecrypted = {
password: WALLET_PASSWORD
};
var algo = fromUserAccount.algo;
nem.crypto.helpers.passwordToPrivatekey(fromUserDecrypted, fromUserAccount, algo);
var fromUserCommon = nem.model.objects.create("common")(WALLET_PASSWORD, fromUserDecrypted.privateKey);
var fromUserKp = nem.crypto.keyPair.create(fromUserDecrypted.privateKey);
// Cosigner-account
var fromUserCosignerDecrypted = {
password: WALLET_PASSWORD
};
algo = fromUserCosignerAccount.algo;
nem.crypto.helpers.passwordToPrivatekey(fromUserCosignerDecrypted, fromUserCosignerAccount, algo);
var fromUserCosignerCommon = nem.model.objects.create("common")(WALLET_PASSWORD, fromUserCosignerDecrypted.privateKey);
var fromUserCosignerKp = nem.crypto.keyPair.create(fromUserCosignerDecrypted.privateKey);
// Create multi-sig transfer transaction
var transferTransaction = nem.model.objects.create("transferTransaction")(toUserAccount.address, amount);
transferTransaction.isMultisig = true;
transferTransaction.multisigAccount = { publicKey: fromUserKp.publicKey.toString() };
var prepareTransferTransaction = nem.model.transactions.prepare("transferTransaction");
var preparedTransferTransaction = prepareTransferTransaction(fromUserCosignerCommon, transferTransaction, NETWORK);
var endpoint = nem.model.objects.create("endpoint")(ENDPOINT, nem.model.nodes.defaultPort);
nem.model.transactions.send(fromUserCosignerCommon, preparedTransferTransaction, endpoint).then((res) => {
console.log(res);
});
}
In order to generate a simple multi-sig transaction we need four things:
- The public key of the multi-sig account we’re sending from
- The private key of the cosignatory account
- The public key of the receiving account
- The amount of XEM we want to send
The first half of the code simply derives the public and private key for the multi-sig and cosignatory accounts respectively. The last piece of the code generates the transfer transaction, prepares it, and broadcasts it to the XEM network.
One of the challenges with the NEM tipbot is in monitoring the incoming registration transactions. To do this we utilize the NEM-sdk’s websocket functionality. Every-time a new block is received we filter out all transactions based on the following criteria:
-
Has this transaction already been processed?
-
Is the receiving address a user that has already registered with the tip bot?
function processBlock(block) {
console.log("Processing block: ");
var transactions = block['transactions'];
User.findAll({
attributes: ['address'],
where: {
address: {
$ne: null
},
$or: [
{
registered: {
$eq: null
}
},
{
registered: {
$eq: false
}
},
]
}
}).then(res => {
var monitoredAddresses = _.pluck(res, 'address');
var filteredTxs = _.filter(transactions, function (tx) {
return _.indexOf(monitoredAddresses, tx.recipient) != -1;
});
for (var i = 0; i < filteredTxs.length; ++i) {
processTransaction(filteredTxs[i]);
}
console.log("Monitored Addresses: " + monitoredAddresses);
console.log("Filtered Txs: " + filteredTxs);
});
}
The remaining transactions are then processed further to verify their contents and purpose. The bulk of this work is done in the processTransaction
function.
The following function is what we use to monitor Reddit comments:
function monitorComments() {
let tipCommentStream = snooStream.commentStream(SUBREDDIT, { regex: /!tipxem\s+((?=.*\d)\d*(?:\.\d+)?)/i, rate: 30000 });
tipCommentStream.on('post', (comment, match) => {
var tipAmount = parseFloat(match[1]).toFixed(6);
// Don't try to tip if this number isn't valid.
if (isFinite(tipAmount) && tipAmount > 0) {
attemptTip(comment, tipAmount);
}
});
}
Every-time a comment is posted we use a regex filter to grab comments that contain the trigger phrase !tipxem
and parse the tip amount.
Future Plans
Future plans to extend the bot include adding support for NEM Authenticator. We also plan to add support for tipping in mosaics as-well.