Skip to content

Commit

Permalink
Merge pull request #9 from canguruhh/dev
Browse files Browse the repository at this point in the history
Issue asset and message
  • Loading branch information
canguruhh authored Dec 12, 2017
2 parents 97f0b9d + 5d4e03d commit 12f0803
Show file tree
Hide file tree
Showing 6 changed files with 278 additions and 48 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,32 @@ wallet.sign(tx)
```
The reward transaction will be generated by the miner of the block that will contain the transaction.

### Issue asset
In order to issue a new asset add an asset issue output to a transaction. Please make sure that the asset symbol is unique. Precision must be in range 0..19 and the maximum supply is must be provided as a quantity of the smallest unit. An asset with 1000 tokens that should have a precision of 2 decimals must be provided as 1000000 and precision 2. The transaction fee must be 10 ETP.
``` javascript
//Transaction object
var tx = new Metaverse.transaction()
//Add inputs
tx.addInput("tFKkLFZ5B29CkMLkYcAd85JxZ8v2joXFDJ","01a37d6434dbc7b8bc61665ad5498096a57150a14118a786cccf1ac165a5c6ab", 0);
//Add outputs
tx.addAssetIssueOutput("SUN.SHINE", 1000000000,4,"coinmaster", "tBcdLMaqR1D3mhyBL7CEWwLPR8yRnPkHBd", "Let it shine");
//Set the change
tx.addOutput("tFKkLFZ5B29CkMLkYcAd85JxZ8v2joXFDJ", "ETP", 22699990000);
```
The asset will be issued to the address defined in the asset issue output.

### Attach message
To attach a message to any transaction there is a function called addMessage.
``` javascript
//Transaction object
var tx = new Metaverse.transaction();
//Add inputs
tx.addInput("tK8UKnBKhk4NQYhSeSb2zeWgMSZaHsn1TY", "15a3a80a867315ee1d3f1ff67e7d5cd0709b1a1d4a48a938f33b01ec8f47425f", 0);
//Add message
tx.addMessage("tK8UKnBKhk4NQYhSeSb2zeWgMSZaHsn1TY", "hi. this is so easy!");
//Don't forget the change
tx.addOutput("tK8UKnBKhk4NQYhSeSb2zeWgMSZaHsn1TY", "ETP", 299990000);
```

## Testing
To run the unit tests just execute:
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metaversejs",
"version": "0.2.25",
"version": "0.2.26",
"description": "MetaverseJS library",
"main": "index.js",
"keywords": [
Expand All @@ -16,7 +16,7 @@
"test": "test"
},
"scripts": {
"test": "mocha -t 20000 test"
"test": "mocha -t 30000 test"
},
"files": [
"src",
Expand Down
230 changes: 185 additions & 45 deletions src/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ function Transaction() {


Transaction.ATTACHMENT_TYPE_ETP_TRANSFER = 0;
Transaction.ATTACHMENT_TYPE_ASSET_TRANSFER = 2;
Transaction.ATTACHMENT_TYPE_ASSET = 2;
Transaction.ATTACHMENT_TYPE_MESSAGE = 3;

Transaction.ASSET_STATUS_ISSUE = 1;
Transaction.ASSET_STATUS_TRANSFER = 2;

Transaction.DEFAULT_FEE = 10000;
Transaction.ASSET_ISSUE_DEFAULT_FEE = 100000;

Transaction.prototype.clone = function(){
Transaction.prototype.clone = function() {
let tx = new Transaction();
tx.version=this.version;
this.inputs.forEach((input)=>{
tx.addInput(input.previous_output.address,input.previous_output.hash,input.previous_output.index,input.previous_output.script);
tx.version = this.version;
this.inputs.forEach((input) => {
tx.addInput(input.previous_output.address, input.previous_output.hash, input.previous_output.index, input.previous_output.script);
});
tx.outputs=JSON.parse(JSON.stringify(this.outputs));
tx.outputs = JSON.parse(JSON.stringify(this.outputs));
return tx;
};

Expand All @@ -46,6 +52,25 @@ Transaction.prototype.addInput = function(previous_output_address, previous_outp
});
};

/**
* Add a message to the transaction.
* @param {String} address
* @param {String} asset
* @param {Number} value
*/
Transaction.prototype.addMessage = function(address, message) {
this.outputs.push({
"address": address,
"attachment": {
type: Transaction.ATTACHMENT_TYPE_MESSAGE,
version: 1,
message: message
},
"script_type": "pubkeyhash",
"value": 0
});
};

/**
* Add an output to the transaction.
* @param {String} address
Expand All @@ -67,11 +92,11 @@ Transaction.prototype.addOutput = function(address, asset, value) {
this.outputs.push({
"address": address,
"attachment": {
"type": Transaction.ATTACHMENT_TYPE_ASSET_TRANSFER,
"type": Transaction.ATTACHMENT_TYPE_ASSET,
"version": 1,
"asset": asset,
"quantity": value,
"status": 2
"status": Transaction.ASSET_STATUS_TRANSFER
},
"script_type": "pubkeyhash",
"value": 0
Expand All @@ -80,31 +105,76 @@ Transaction.prototype.addOutput = function(address, asset, value) {
};

/**
* Add locked etp output to the transaction.
* @param {String} address
* @param {Number} value
* @param {Number} locktime Number of blocks
* Add an asset issue output to the transaction.
* @param {String} symbol Up to 63 alphanumeric or . characters
* @param {Number} max_supply
* @param {Number} precision Number of decimals from range 0..19
* @param {String} issuer Name of issuer length < 64 characters
* @param {String} address Recepient address
* @param {String} description Description for asset < 64 characters
*/
Transaction.prototype.addLockOutput = function(address, value, locktime) {
switch(locktime){
case 25200:
case 108000:
case 331200:
case 655200:
case 1314000:
Transaction.prototype.addAssetIssueOutput = function(symbol, max_supply, precision, issuer, address, description) {
if (!/^([A-Z0-9\.]{3,63})$/.test(symbol))
throw Error('ERR_SYMBOL_NAME');
else if (!/^([A-Za-z0-9\.]{3,63})$/.test(issuer))
throw Error('ERR_ISSUER_NAME');
else if (max_supply <= 0)
throw Error('ERR_MAX_SUPPLY_TOO_LOW');
else if (precision < 0)
throw Error('ERR_PRECISION_NEGATIVE');
else if (precision >= 20)
throw Error('ERR_PRECISION_TOO_HIGH');
else if (description.length >= 64)
throw Error('ERR_DESCRIPTION_TOO_LONG');
else if (description.length >= 64)
throw Error('ERR_DESCRIPTION_TOO_LONG');
else if (!Transaction.isAddress(address))
throw Error('ERR_ADDRESS_FORMAT');
else
this.outputs.push({
"address": address,
"attachment": {
type: Transaction.ATTACHMENT_TYPE_ETP_TRANSFER,
version: 1
"type": Transaction.ATTACHMENT_TYPE_ASSET,
"version": 1,
"status": Transaction.ASSET_STATUS_ISSUE,
"symbol": symbol,
"max_supply": max_supply,
"precision": precision,
"issuer": issuer,
"address": address,
"description": description
},
"value": value,
"script_type": "lock",
"locktime": locktime
"script_type": "pubkeyhash",
"value": 0
});
break;
default:
throw "Illegal locktime";
};

/**
* Add locked etp output to the transaction.
* @param {String} address
* @param {Number} value
* @param {Number} locktime Number of blocks
*/
Transaction.prototype.addLockOutput = function(address, value, locktime) {
switch (locktime) {
case 25200:
case 108000:
case 331200:
case 655200:
case 1314000:
this.outputs.push({
"address": address,
"attachment": {
type: Transaction.ATTACHMENT_TYPE_ETP_TRANSFER,
version: 1
},
"value": value,
"script_type": "lock",
"locktime": locktime
});
break;
default:
throw "Illegal locktime";
}
};

Expand Down Expand Up @@ -167,13 +237,13 @@ function encodeOutputs(outputs) {
//Write value as 8byte integer
offset = bufferutils.writeUInt64LE(buffer, output.value, offset);
//Output script
if(output.script_type==="pubkeyhash"){
if (output.script_type === "pubkeyhash") {
offset = writeScriptPayToPubKeyHash(output.address, buffer, offset);
} else if(output.script_type==="lock"){
} else if (output.script_type === "lock") {
let locktime_le_string = parseInt(output.locktime).toString(16).replace(/^(.(..)*)$/, "0$1").match(/../g).reverse().join("");
offset = writeScriptLockedPayToPubKeyHash(output.address, locktime_le_string, buffer, offset);
} else{
throw 'Unknown script type: '+output.script_type;
} else {
throw 'Unknown script type: ' + output.script_type;
}

// attachment
Expand All @@ -183,8 +253,20 @@ function encodeOutputs(outputs) {
switch (output.attachment.type) {
case Transaction.ATTACHMENT_TYPE_ETP_TRANSFER:
break;
case Transaction.ATTACHMENT_TYPE_ASSET_TRANSFER:
offset = Transaction.encodeAttachmentAssetTransfer(buffer, offset, output.attachment);
case Transaction.ATTACHMENT_TYPE_ASSET:
switch (output.attachment.status) {
case Transaction.ASSET_STATUS_ISSUE:
offset = Transaction.encodeAttachmentAssetIssue(buffer, offset, output.attachment);
break;
case Transaction.ASSET_STATUS_TRANSFER:
offset = Transaction.encodeAttachmentAssetTransfer(buffer, offset, output.attachment);
break;
default:
throw Error("Asset status unknown");
}
break;
case Transaction.ATTACHMENT_TYPE_MESSAGE:
offset = Transaction.encodeAttachmentMessage(buffer, offset, output.attachment.message);
break;
default:
throw Error("What kind of an output is that?!");
Expand Down Expand Up @@ -223,10 +305,10 @@ function encodeInputs(inputs, add_address_to_previous_output_index) {
if (index == add_address_to_previous_output_index) {
let lockregex = /^\[\ ([a-f0-9]+)\ \]\ numequalverify dup\ hash160\ \[ [a-f0-9]+\ \]\ equalverify\ checksig$/gi;
//Check if previous output was locked before
if(input.previous_output.script && input.previous_output.script.match(lockregex)){
if (input.previous_output.script && input.previous_output.script.match(lockregex)) {
let locktime = lockregex.exec(input.previous_output.script.match(lockregex)[0])[1];
offset = writeScriptLockedPayToPubKeyHash(input.previous_output.address, locktime , buffer, offset);
} else{
offset = writeScriptLockedPayToPubKeyHash(input.previous_output.address, locktime, buffer, offset);
} else {
//Previous output was p2pkh
offset = writeScriptPayToPubKeyHash(input.previous_output.address, buffer, offset);
}
Expand Down Expand Up @@ -257,6 +339,21 @@ function encodeLockTime(lock_time) {
return buffer;
}

/**
* Helper function to encode the attachment for a message.
* @param {Buffer} buffer
* @param {Number} offset
* @param {string} message
* @returns {Number} New offset
* @throws {Error}
*/
Transaction.encodeAttachmentMessage = function(buffer, offset, message) {
if (message == undefined)
throw Error('Specify message');
offset += encodeString(buffer, message, offset);
return offset;
};

/**
* Helper function to encode the attachment for an asset transfer.
* @param {Buffer} buffer
Expand All @@ -265,18 +362,46 @@ function encodeLockTime(lock_time) {
* @returns {Number} New offset
* @throws {Error}
*/
Transaction.encodeAttachmentAssetTransfer = function(buffer, offset, attachment_data_type) {
if (attachment_data_type.asset == undefined)
Transaction.encodeAttachmentAssetTransfer = function(buffer, offset, attachment_data) {
if (attachment_data.asset == undefined)
throw Error('Specify output asset');
if (attachment_data_type.quantity == undefined)
if (attachment_data.quantity == undefined)
throw Error('Specify output quanity');
offset = buffer.writeUInt32LE(attachment_data_type.status, offset);
offset = buffer.writeUInt8(attachment_data_type.asset.length, offset);
offset += new Buffer(attachment_data_type.asset).copy(buffer, offset);
offset = bufferutils.writeUInt64LE(buffer, attachment_data_type.quantity, offset);
offset = buffer.writeUInt32LE(attachment_data.status, offset);
offset = buffer.writeUInt8(attachment_data.asset.length, offset);
offset += new Buffer(attachment_data.asset).copy(buffer, offset);
offset = bufferutils.writeUInt64LE(buffer, attachment_data.quantity, offset);
return offset;
};

/**
* Helper function to encode the attachment for a new asset.
* @param {Buffer} buffer
* @param {Number} offset
* @param {Number} attachment_data
* @returns {Number} New offset
* @throws {Error}
*/
Transaction.encodeAttachmentAssetIssue = function(buffer, offset, attachment_data) {
offset = buffer.writeUInt32LE(attachment_data.status, offset);
//Encode symbol
offset += encodeString(buffer, attachment_data.symbol, offset);
//Encode maximum supply
offset = bufferutils.writeUInt64LE(buffer, attachment_data.max_supply, offset);
//Encode precision
offset = buffer.writeUInt8(attachment_data.precision, offset);
offset += buffer.write("000000", offset, 3, 'hex')

//Encode issuer
offset += encodeString(buffer, attachment_data.issuer, offset);
//Encode recipient address
offset += encodeString(buffer, attachment_data.address, offset);
//Encode description
offset += encodeString(buffer, attachment_data.description, offset);
return offset;
};


/**
* Enodes the given input script.
* @param {String} script_string
Expand Down Expand Up @@ -325,8 +450,8 @@ function writeScriptPayToPubKeyHash(address, buffer, offset) {
* @returns {Number} new offset
*/
function writeScriptLockedPayToPubKeyHash(address, locktime, buffer, offset) {
let locktime_buffer=new Buffer(locktime, 'hex');
offset = buffer.writeUInt8(27+locktime_buffer.length, offset); //Script length
let locktime_buffer = new Buffer(locktime, 'hex');
offset = buffer.writeUInt8(27 + locktime_buffer.length, offset); //Script length
offset = buffer.writeUInt8(locktime_buffer.length, offset); //Length of locktime
offset += locktime_buffer.copy(buffer, offset);
offset = buffer.writeUInt8(OPS.OP_NUMEQUALVERIFY, offset);
Expand All @@ -339,4 +464,19 @@ function writeScriptLockedPayToPubKeyHash(address, locktime, buffer, offset) {
offset = buffer.writeUInt8(OPS.OP_CHECKSIG, offset);
return offset;
}

function encodeString(buffer, str, offset) {
var payload = new Buffer.from(str, 'utf-8');
if (payload.length < 0xfd) {
offset = buffer.writeUInt8(payload.length, offset);
} else if (payload.length <= 0xffff) {
offset = buffer.writeUInt8(0xfd, offset);
offset = buffer.writeInt16LE(payload.length, offset);
} else
throw Error("Wow so much data!");
return payload.copy(buffer, offset) + 1;
}

module.exports = Transaction;

Transaction.isAddress = (address) => (address.length == 34) && (address.charAt(0) == 'M' || address.charAt(0) == 't' || address.charAt(0) == '3');
Loading

0 comments on commit 12f0803

Please sign in to comment.