jingtum-lib
Version:
jingtum lib
1,784 lines (1,574 loc) • 53.5 kB
JavaScript
/**
* Type definitions for binary format and
* utils to handle format conversions.
*
* This file should not be included directly. Instead, find the format you're
* trying to parse or serialize in binformat.js and pass that to
* SerializedObject.parse() or SerializedObject.serialize().
*/
var assert = require('assert');
var extend = require('extend');
var base_wallet = require('jingtum-base-lib').Wallet;
var convertBytesToAddress = require('jingtum-base-lib/src/keypairs.js').convertBytesToAddress;
var convertAddressToBytes = require('jingtum-base-lib/src/keypairs.js').convertAddressToBytes;
var BN = require('bn.js');
var tum = require('./TumAmount');
var Amount = tum.Amount;
const CURRENCY_NAME_LEN = 3;//货币长度
const CURRENCY_NAME_LEN2 = 6;//货币长度
/**
* Data type map.
*
* Mapping of type ids to data types. The type id is specified by the high
*
*/
var TYPES_MAP = [
void(0),
// Common
'Int16', // 1
'Int32', // 2
'Int64', // 3
'Hash128', // 4
'Hash256', // 5
'Amount', // 6
'VL', // 7
'Account', // 8
// 9-13 reserved
void(0), // 9
void(0), // 10
void(0), // 11
void(0), // 12
void(0), // 13
'Object', // 14
'Array', // 15
// Uncommon
'Int8', // 16
'Hash160', // 17
'PathSet', // 18
'Vector256' // 19
];
/**
* Field type map.
*
* Mapping of field type id to field type name.
*/
var FIELDS_MAP = {
// Common types
1: { // Int16
1: 'LedgerEntryType',
2: 'TransactionType',
3: 'SignerWeight'
},
2: { // Int32
2: 'Flags',
3: 'SourceTag',
4: 'Sequence',
5: 'PreviousTxnLgrSeq',
6: 'LedgerSequence',
7: 'CloseTime',
8: 'ParentCloseTime',
9: 'SigningTime',
10: 'Expiration',
11: 'TransferRate',
12: 'WalletSize',
13: 'OwnerCount',
14: 'DestinationTag',
// Skip 15
15: "Timestamp",
16: 'HighQualityIn',
17: 'HighQualityOut',
18: 'LowQualityIn',
19: 'LowQualityOut',
20: 'QualityIn',
21: 'QualityOut',
22: 'StampEscrow',
23: 'BondAmount',
24: 'LoadFee',
25: 'OfferSequence',
26: 'FirstLedgerSequence',
27: 'LastLedgerSequence',
28: 'TransactionIndex',
29: 'OperationLimit',
30: 'ReferenceFeeUnits',
31: 'ReserveBase',
32: 'ReserveIncrement',
33: 'SetFlag',
34: 'ClearFlag',
35: "RelationType",
36: 'Method',
37: 'AppType',
38: 'SignerQuorum',
39: 'Contracttype'
},
3: { // Int64
1: 'IndexNext',
2: 'IndexPrevious',
3: 'BookNode',
4: 'OwnerNode',
5: 'BaseFee',
6: 'ExchangeRate',
7: 'LowNode',
8: 'HighNode',
9: 'OfferFeeRateNum',
10: 'OfferFeeRateDen',
13: 'TokenSize',
14: 'TokenIssued'
},
4: { // Hash128
1: 'EmailHash'
},
5: { // Hash256
1: 'LedgerHash',
2: 'ParentHash',
3: 'TransactionHash',
4: 'AccountHash',
5: 'PreviousTxnID',
6: 'LedgerIndex',
7: 'WalletLocator',
8: 'RootIndex',
9: 'AccountTxnID',
16: 'BookDirectory',
17: 'InvoiceID',
18: 'Nickname',
19: 'Amendment',
20: 'TicketID',
21: 'TokenID'
},
6: { // Amount
1: 'Amount',
2: 'Balance',
3: 'LimitAmount',
4: 'TakerPays',
5: 'TakerGets',
6: 'LowLimit',
7: 'HighLimit',
8: 'Fee',
9: 'SendMax',
16: 'MinimumOffer',
17: 'JingtumEscrow',
18: 'DeliveredAmount'
},
7: { // VL
1: 'PublicKey',
2: 'MessageKey',
3: 'SigningPubKey',
4: 'TxnSignature',
5: 'Generator',
6: 'Signature',
7: 'Domain',
8: 'FundCode',
9: 'RemoveCode',
10: 'ExpireCode',
11: 'CreateCode',
12: 'MemoType',
13: 'MemoData',
14: 'MemoFormat',
15: 'Payload',
17: 'ContractMethod',
18: 'Parameter',
20: 'MethodSignature',
21: 'InfoData',
22: 'InfoType'
},
8: { // Account
1: 'Account',
2: 'Owner',
3: 'Destination',
4: 'Issuer',
7: 'Target',
8: 'RegularKey',
9: 'FeeAccountID',
13: 'Platform',
14: 'TokenOwner',
15: 'AuthorizedAccount'
},
14: { // Object
1: void(0), //end of Object
2: 'TransactionMetaData',
3: 'CreatedNode',
4: 'DeletedNode',
5: 'ModifiedNode',
6: 'PreviousFields',
7: 'FinalFields',
8: 'NewFields',
9: 'TemplateEntry',
10: 'Memo',
11: 'Arg',
12: 'SignerEntry',
13: 'Signer',
15: 'TokenInfo'
},
15: { // Array
1: void(0), //end of Array
2: 'SigningAccounts',
3: 'TxnSignatures',
4: 'Signatures',
5: 'Template',
6: 'Necessary',
7: 'Sufficient',
8: 'AffectedNodes',
9: 'Memos',
10: 'Args',
11: 'SignerEntries',
12: 'Signers',
14: 'TokenInfos'
},
// Uncommon types
16: { // Int8
1: 'CloseResolution',
2: 'TemplateEntryType',
3: 'TransactionResult',
4: 'ContractParamsType'
},
17: { // Hash160
1: 'TakerPaysCurrency',
2: 'TakerPaysIssuer',
3: 'TakerGetsCurrency',
4: 'TakerGetsIssuer'
},
18: { // PathSet
1: 'Paths'
},
19: { // Vector256
1: 'Indexes',
2: 'Hashes',
3: 'Amendments'
}
};
/*
* Inverse of the fields map
*
*/
var INVERSE_FIELDS_MAP = {
LedgerEntryType: [1, 1],
TransactionType: [1, 2],
SignerWeight: [1, 3],
Flags: [2, 2],
SourceTag: [2, 3],
Sequence: [2, 4],
PreviousTxnLgrSeq: [2, 5],
LedgerSequence: [2, 6],
CloseTime: [2, 7],
ParentCloseTime: [2, 8],
SigningTime: [2, 9],
Expiration: [2, 10],
TransferRate: [2, 11],
WalletSize: [2, 12],
OwnerCount: [2, 13],
DestinationTag: [2, 14],
Timestamp: [2, 15],
HighQualityIn: [2, 16],
HighQualityOut: [2, 17],
LowQualityIn: [2, 18],
LowQualityOut: [2, 19],
QualityIn: [2, 20],
QualityOut: [2, 21],
StampEscrow: [2, 22],
BondAmount: [2, 23],
LoadFee: [2, 24],
OfferSequence: [2, 25],
FirstLedgerSequence: [2, 26],
LastLedgerSequence: [2, 27],
TransactionIndex: [2, 28],
OperationLimit: [2, 29],
ReferenceFeeUnits: [2, 30],
ReserveBase: [2, 31],
ReserveIncrement: [2, 32],
SetFlag: [2, 33],
ClearFlag: [2, 34],
RelationType: [2, 35],
Method: [2, 36],
AppType: [2, 37],
SignerQuorum: [2, 38],
Contracttype: [2, 39],
IndexNext: [3, 1],
IndexPrevious: [3, 2],
BookNode: [3, 3],
OwnerNode: [3, 4],
BaseFee: [3, 5],
ExchangeRate: [3, 6],
LowNode: [3, 7],
HighNode: [3, 8],
OfferFeeRateNum: [3,9],
OfferFeeRateDen: [3,10],
TokenSize: [3,13],
TokenIssued: [3,14],
EmailHash: [4, 1],
LedgerHash: [5, 1],
ParentHash: [5, 2],
TransactionHash: [5, 3],
AccountHash: [5, 4],
PreviousTxnID: [5, 5],
LedgerIndex: [5, 6],
WalletLocator: [5, 7],
RootIndex: [5, 8],
AccountTxnID: [5, 9],
BookDirectory: [5, 16],
InvoiceID: [5, 17],
Nickname: [5, 18],
Amendment: [5, 19],
TicketID: [5, 20],
TokenID: [5, 21],
Amount: [6, 1],
Balance: [6, 2],
LimitAmount: [6, 3],
TakerPays: [6, 4],
TakerGets: [6, 5],
LowLimit: [6, 6],
HighLimit: [6, 7],
Fee: [6, 8],
SendMax: [6, 9],
MinimumOffer: [6, 16],
JingtumEscrow: [6, 17],
DeliveredAmount: [6, 18],
PublicKey: [7, 1],
MessageKey: [7, 2],
SigningPubKey: [7, 3],
TxnSignature: [7, 4],
Generator: [7, 5],
Signature: [7, 6],
Domain: [7, 7],
FundCode: [7, 8],
RemoveCode: [7, 9],
ExpireCode: [7, 10],
CreateCode: [7, 11],
MemoType: [7, 12],
MemoData: [7, 13],
MemoFormat: [7, 14],
Payload: [7, 15],
ContractMethod: [7, 17],
Parameter: [7, 18],
MethodSignature: [7, 20],
InfoData: [7, 21],
InfoType: [7, 22],
Account: [8, 1],
Owner: [8, 2],
Destination: [8, 3],
Issuer: [8, 4],
Target: [8, 7],
RegularKey: [8, 8],
FeeAccountID: [8, 9],
Platform: [8, 13],
TokenOwner: [8, 14],
AuthorizedAccount: [8, 15],
undefined: [15, 1],
TransactionMetaData: [14, 2],
CreatedNode: [14, 3],
DeletedNode: [14, 4],
ModifiedNode: [14, 5],
PreviousFields: [14, 6],
FinalFields: [14, 7],
NewFields: [14, 8],
TemplateEntry: [14, 9],
Memo: [14, 10],
Arg: [14, 11],
SignerEntry: [14, 12],
Signer: [14, 13],
TokenInfo: [14, 15],
SigningAccounts: [15, 2],
TxnSignatures: [15, 3],
Signatures: [15, 4],
Template: [15, 5],
Necessary: [15, 6],
Sufficient: [15, 7],
AffectedNodes: [15, 8],
Memos: [15, 9],
Args: [15, 10],
SignerEntries: [15, 11],
Signers: [15, 12],
TokenInfos: [15, 14],
CloseResolution: [16, 1],
TemplateEntryType: [16, 2],
TransactionResult: [16, 3],
ContractParamsType: [16, 4],
TakerPaysCurrency: [17, 1],
TakerPaysIssuer: [17, 2],
TakerGetsCurrency: [17, 3],
TakerGetsIssuer: [17, 4],
Paths: [18, 1],
Indexes: [19, 1],
Hashes: [19, 2],
Amendments: [19, 3]
};
var TRANSACTION_RESULTS = {
tesSUCCESS: 0,
tecCLAIM: 100,
tecPATH_PARTIAL: 101,
tecUNFUNDED_ADD: 102,
tecUNFUNDED_OFFER: 103,
tecUNFUNDED_PAYMENT: 104,
tecFAILED_PROCESSING: 105,
tecDIR_FULL: 121,
tecINSUF_RESERVE_LINE: 122,
tecINSUF_RESERVE_OFFER: 123,
tecNO_DST: 124,
tecNO_DST_INSUF_SWT: 125,
tecNO_LINE_INSUF_RESERVE: 126,
tecNO_LINE_REDUNDANT: 127,
tecPATH_DRY: 128,
tecMASTER_DISABLED: 130,
tecNO_REGULAR_KEY: 131,
tecOWNERS: 132,
tecNO_ISSUER: 133,
tecNO_AUTH: 134,
tecNO_LINE: 135,
tecINSUFF_FEE: 136,
tecFROZEN: 137,
tecNO_TARGET: 138,
tecNO_PERMISSION: 139,
tecNO_ENTRY: 140,
tecINSUFFICIENT_RESERVE: 141
};
var SerializedType = function (methods) {
extend(this, methods);
};
function isNumber(val) {
return typeof val === 'number' && isFinite(val);
};
function isString(val) {
return typeof val === 'string';
};
/*
* Use RegExp match function
* perform case-insensitive matching
* for HEX chars 0-9 and a-f
*/
function isHexInt64String(val) {
return isString(val) && /^[0-9A-F]{0,16}$/i.test(val);
}
function isHexString(val) {
return isString(val) && /^[0-9A-F]+$/i.test(val);
}
function isCurrencyString(val) {
return isString(val) && /^[A-Z0-9]{3}$/.test(val);
}
function isBigInteger(val) {
return val instanceof BigInteger;
}
/*
* convert a HEX to dec number
* 0-9 to the same digit
* a-f, A-F to 10 - 15,
* all others to 0
*/
function getDecFromHexChar(in_char) {
if (in_char.length > 1)
return 0;
var asc_code = in_char.charCodeAt(0);
if (asc_code > 48) {
if (asc_code < 58) {
//digit 1-9
return asc_code - 48;
}
else {
if (asc_code > 64) {
if (asc_code < 91)
//letter A-F
return asc_code - 55;
else {
if (asc_code > 96 && asc_code < 123)
return asc_code - 87;
}
}
}
}
return 0;
}
/*
* Convert a HEX string to byte array
* for a string, returns as byte array
* Input is not even, add 0 to the end.
* a0c -> a0 c0
*/
function convertHexToByteArray(in_str) {
//If the input HEX string is odd,
if (in_str.length % 2 != 0)
in_str = in_str + '0';
return new BN(in_str, 16).toArray(null, in_str.length / 2);
}
/*
* Input: HEX data in string format
* Output: byte array
*/
function serializeHex(so, hexData, noLength) {
var byteData = convertHexToByteArray(hexData);//bytes.fromBits(hex.toBits(hexData));
if (!noLength) {
SerializedType.serialize_varint(so, byteData.length);
}
so.append(byteData);
}
/*
* Convert an Account to byte array
* for serialization.
* Input: a string represents the Account/Issuer.
* Output: Bytes array contains the Account info.
*/
function convertUint160ToByteArray(in_str) {
var i, out = [], len;
var str = in_str.replace(/\s|0x/g, "");
if (base_wallet.isValidAddress(str)) {
out = convertAddressToBytes(str);
} else {
throw new Error('Invalid input account.');
}
return out;
}
/*
* Convert the byte array to HEX values as String
* Input is 32-bits(byte) array
* Output is String with ordered sequence of 16-bit values contains only 0-9 and A-F
*/
function convertByteArrayToHex(byte_array) {
return byte_array.map(function (byteValue) {
var hex = byteValue.toString(16).toUpperCase();
return hex.length > 1 ? hex : '0' + hex;
}).join('');
}
/*
* input: UTF8 coding string
* output: HEX code
*/
function convertStringToHex(in_str) {
var str = unescape(encodeURIComponent(in_str));
var out_str = "", i, tmp = 0;
for (i = 0; i < str.length; i++) {
out_str += (" 00" + Number(str.charCodeAt(i)).toString(16)).substr(-2);
}
return out_str.toUpperCase();//hex.fromBits(utf8.toBits(in_str)).toUpperCase());
}
function convertHexToString(hexString) {
var out_str = "", i, tmp = 0;
for (i = 0; i < hexString.length; i += 2) {
var tmp = '0x' + (hexString.slice(i, i + 2));
out_str += String.fromCharCode(parseInt(tmp));
}
return decodeURIComponent(escape(out_str));//out_str.toUpperCase();/
}
/* For test functions only*/
function typeTest(in_str) {
var b1 = convertAddressToBytes(in_str);
// var b1 = UInt160.from_json(in_str).to_bytes();
var b2 = convertUint160ToByteArray(in_str);
// base_wallet.
console.log(typeof(b1), typeof(b2));
if (b1.length == b2.length) {
for (var i = 0; i < b1.length; i++)
console.log(b1[i], ' vs ', b2[i]);
if (b1[i] != b2[i])
console.log("Not equal at ", i);
} else
console.log(b1.length, ' not equal ', b2.length);
console.log("------------Test on convert to HEX----------\n");
// console.log(UInt160.from_json(in_str).to_hex());
console.log(convertByteArrayToHex(b2));
return;
}
exports.typeTest = typeTest;
/*
* used by Amount serialize
*/
function arraySet(count, value) {
var a = new Array(count);
for (var i = 0; i < count; i++) {
a[i] = value;
}
return a;
}
SerializedType.serialize_varint = function (so, val) {
if (val < 0) {
throw new Error('Variable integers are unsigned.');
}
if (val <= 192) {
so.append([val]);
} else if (val <= 12480) {
val -= 193;
so.append([193 + (val >>> 8), val & 0xff]);
} else if (val <= 918744) {
val -= 12481;
so.append([241 + (val >>> 16), val >>> 8 & 0xff, val & 0xff]);
} else {
throw new Error('Variable integer overflow.');
}
};
SerializedType.prototype.parse_varint = function (so) {
var b1 = so.read(1)[0], b2, b3;
var result;
if (b1 > 254) {
throw new Error('Invalid varint length indicator');
}
if (b1 <= 192) {
result = b1;
} else if (b1 <= 240) {
b2 = so.read(1)[0];
result = 193 + (b1 - 193) * 256 + b2;
} else if (b1 <= 254) {
b2 = so.read(1)[0];
b3 = so.read(1)[0];
result = 12481 + (b1 - 241) * 65536 + b2 * 256 + b3;
}
return result;
};
// In the following, we assume that the inputs are in the proper range. Is this correct?
// Helper functions for 1-, 2-, and 4-byte integers.
/**
* Convert an integer value into an array of bytes.
*
* The result is appended to the serialized object ('so').
*/
function convertIntegerToByteArray(val, bytes) {
if (!isNumber(val)) {
throw new Error('Value is not a number', bytes);
}
if (val < 0 || val >= Math.pow(256, bytes)) {
throw new Error('Value out of bounds');
}
var newBytes = [];
for (var i = 0; i < bytes; i++) {
newBytes.unshift(val >>> (i * 8) & 0xff);
}
return newBytes;
}
// Convert a certain number of bytes from the serialized object ('so') into an integer.
function readAndSum(so, bytes) {
var sum = 0;
if (bytes > 4) {
throw new Error('This function only supports up to four bytes.');
}
for (var i = 0; i < bytes; i++) {
var byte = so.read(1)[0];
sum += (byte << (8 * (bytes - i - 1)));
}
// Convert to unsigned integer
return sum >>> 0;
}
var STInt8 = exports.Int8 = new SerializedType({
serialize: function (so, val) {
so.append(convertIntegerToByteArray(val, 1));
},
parse: function (so) {
return readAndSum(so, 1);
}
});
STInt8.id = 16;
var STInt16 = exports.Int16 = new SerializedType({
serialize: function (so, val) {
so.append(convertIntegerToByteArray(val, 2));
},
parse: function (so) {
return readAndSum(so, 2);
}
});
STInt16.id = 1;
var STInt32 = exports.Int32 = new SerializedType({
serialize: function (so, val) {
so.append(convertIntegerToByteArray(val, 4));
},
parse: function (so) {
return readAndSum(so, 4);
}
});
STInt32.id = 2;
/*
* Convert int64 big number input
* to HEX string, then serialize it.
* -2,147,483,648 to +2,147,483,648
*/
var STInt64 = exports.Int64 = new SerializedType({
serialize: function (so, val) {
var big_num_in_hex_str;//NumObject;
if (isNumber(val)) {
val = Math.floor(val);
if (val < 0) {
throw new Error('Negative value for unsigned Int64 is invalid.');
}
//bigNumObject = new BigInteger(String(val), 10);
var bn = new BN(val, 10);
big_num_in_hex_str = bn.toString(16);
// var a = new BN('dead', 16);
// var b = new BN('101010', 2);
} else if (isString(val)) {
//
if (!isHexInt64String(val)) {
throw new Error('Not a valid hex Int64.');
}
big_num_in_hex_str = val;
} else {
throw new Error('Invalid type for Int64');
}
if (big_num_in_hex_str.length > 16) {
throw new Error('Int64 is too large');
}
while (big_num_in_hex_str.length < 16) {
big_num_in_hex_str = '0' + big_num_in_hex_str;
}
serializeHex(so, big_num_in_hex_str, true); //noLength = true
},
parse: function (so) {
var bytes = so.read(8);
// We need to add a 0, so if the high bit is set it won't think it's a
// pessimistic numeric fraek. What doth lief?
var result = new BigInteger([0].concat(bytes), 256);
assert(result instanceof BigInteger);
return result;
}
});
STInt64.id = 3;
/*
* serialize
* Input: HEX value for a 128 bit Int
* Output: byte array of the value appended to the buffer.
* parse
* Input: byte array
* Output: HEX value
*/
var STHash128 = exports.Hash128 = new SerializedType({
serialize: function (so, val) {
//var hash = UInt128.from_json(val);
if (isString(val) && /^[0-9A-F]{0,16}$/i.test(val)
&& val.length <= 32) {
serializeHex(so, val, true); //noLength = true
} else {
throw new Error('Invalid Hash128');
}
},
parse: function (so) {
var val = so.read(16);
if (!Array.isArray(val) || val.length !== 16) {
//this._value = NaN;
return NaN;
} else {
//this._value = new BigInteger([0].concat(j), 256);
//TODO: need to verify
return NaN;//new BigNumber(val, 256);
}
//return UInt128.from_bytes(so.read(16));
}
});
STHash128.id = 4;
var STHash256 = exports.Hash256 = new SerializedType({
serialize: function (so, val) {
if (isString(val) && /^[0-9A-F]{0,64}$/i.test(val)
&& val.length <= 64) {
serializeHex(so, val, true); //noLength = true
} else {
throw new Error('Invalid Hash256');
}
},
parse: function (so) {
//return UInt256.from_bytes(so.read(32));
console.log("TODO:");
return NaN;
}
});
STHash256.id = 5;
/*
* Convert the HASH160 to bytes array
* and back
*/
var STHash160 = exports.Hash160 = new SerializedType({
serialize: function (so, val) {
serializeHex(so, convertHexToByteArray(val), true);
},
parse: function (so) {
return convertBytesToAddress(so.read(20));
}
});
STHash160.id = 17;
// Internal
/*
* Should handle
*/
var STCurrency = new SerializedType({
//Convert the input JSON format data INTO a BYTE array
from_json_to_bytes: function(j, shouldInterpretSWT) {
//return (new Currency()).parse_json(j, shouldInterpretSWT);
console.log("Handle input json format currency:", j, typeof(j));
var val = new Array(20);//return byte array representing currency code
for (var i=0; i<20; i++) {
val[i] = 0;
}
switch (typeof j){
case 'string':
//For Tum code with 40 chars, such as
//800000000000000000000000A95EFD7EC3101635
//treat as HEX string, convert to the 20 bytes array
if ( isHexString(j) && j.length == 40 ) {
val = convertHexToByteArray(j);
}else if (isCurrencyString(j)) {
//For Tum code with 3 letters/digits, such as
//CNY, USD,
//treat
// var currencyCode = j.toUpperCase();
var currencyCode = j;
if (currencyCode.length >= CURRENCY_NAME_LEN && currencyCode.length <= CURRENCY_NAME_LEN2){
var end = 14;
var len = currencyCode.length - 1;
for(var x = len; x >= 0; x--){
val[end - x] = currencyCode.charCodeAt(len - x) & 0xff;
}
}
}else{
//Input not match the naming format
//Throw error
throw new Error("Input tum code not valid!");
}
break;
case 'number':
//TODO, follow the Tum code rules
console.log("Nmber");
throw new Error("Input tum code not valid!");
if (!isNaN(j)) {
this.parse_number(j);
}
break;
case 'object':
console.log("Object");
throw new Error("Input tum code not valid!");
break;
}
return val;
},
serialize: function (so, val, swt_as_ascii) {
var currencyData = val.to_bytes();
if (!currencyData) {
throw new Error('Tried to serialize invalid/unimplemented currency type.');
}
so.append(currencyData);
},
//Convert the Tum/Currency code from a 20 bytes array
//TODO, check the parse value
from_bytes: function(j) {
if (!Array.isArray(j) || j.length !== 20) {
return NaN;
} else {
return new bn([0].concat(j), 256);
}
},
parse: function (so) {
var bytes = so.read(20);
var currency = this.from_bytes(bytes);
// XXX Disabled check. Theoretically, the Currency class should support any
// UInt160 value and consider it valid. But it doesn't, so for the
// deserialization to be usable, we need to allow invalid results for now.
//if (!currency.is_valid()) {
// throw new Error('Invalid currency: '+convertByteArrayToHex(bytes));
//}
return currency;
}
});
var STAmount = exports.Amount = new SerializedType({
serialize: function (so, val) {
var amount = Amount.from_json(val);
if (!amount.is_valid()) {
throw new Error('Not a valid Amount object.');
}
// Amount (64-bit integer)
var valueBytes = arraySet(8, 0);
//For SWT, offset is 0
//only convert the value
if (amount.is_native()) {
var bn = new BN(amount._value, 10);
var valueHex = bn.toString(16);
// Enforce correct length (64 bits)
if (valueHex.length > 16) {
throw new Error('Amount Value out of bounds');
}
while (valueHex.length < 16) {
valueHex = '0' + valueHex;
}
//Convert the HEX value to bytes array
valueBytes = convertHexToByteArray(valueHex);//bytes.fromBits(hex.toBits(valueHex));
// Clear most significant two bits - these bits should already be 0 if
// Amount enforces the range correctly, but we'll clear them anyway just
// so this code can make certain guarantees about the encoded value.
valueBytes[0] &= 0x3f;
if (!amount.is_negative()) {
valueBytes[0] |= 0x40;
}
so.append(valueBytes);
} else {
//For other non-native currency
//1. Serialize the currency value with offset
//Put offset
var hi = 0, lo = 0;
// First bit: non-native
hi |= 1 << 31;
if (!amount.is_zero()) {
// Second bit: non-negative?
if (!amount.is_negative()) {
hi |= 1 << 30;
}
// Next eight bits: offset/exponent
hi |= ((97 + amount._offset) & 0xff) << 22;
// Remaining 54 bits: mantissa
hi |= amount._value.shrn(32).toNumber() & 0x3fffff;
lo = amount._value.toNumber() & 0xffffffff;
}
/** Convert from a bitArray to an array of bytes.
**/
var arr = [hi, lo];
var l = arr.length, x, bl, i, tmp;
if (l === 0) {
bl = 0;
} else {
x = arr[l - 1];
bl = (l - 1) * 32 + (Math.round(x / 0x10000000000) || 32);
}
//Setup a new byte array and filled the byte data in
//Results should not longer than 8 bytes as defined earlier
var tmparray = [];
for (i = 0; i < bl / 8; i++) {
if ((i & 3) === 0) {
tmp = arr[i / 4];
}
tmparray.push(tmp >>> 24);
// console.log("newPush:", i, tmp >>>24);
tmp <<= 8;
}
if (tmparray.length > 8) {
throw new Error('Invalid byte array length in AMOUNT value representation');
}
valueBytes = tmparray;
so.append(valueBytes);
//2. Serialize the currency info with currency code
// and issuer
//console.log("Serial non-native AMOUNT ......");
// Currency (160-bit hash)
var tum_bytes = amount.tum_to_bytes();
so.append(tum_bytes);
// Issuer (160-bit hash)
//so.append(amount.issuer().to_bytes());
so.append(convertAddressToBytes(amount.issuer()));
}
},
parse: function (so) {
var amount = new Amount();
var value_bytes = so.read(8);
var is_zero = !(value_bytes[0] & 0x7f);
for (var i = 1; i < 8; i++) {
is_zero = is_zero && !value_bytes[i];
}
if (value_bytes[0] & 0x80) {
//non-native
var currency = STCurrency.parse(so);
var issuer_bytes = so.read(20);
var issuer = convertBytesToAddress(issuer_bytes);//UInt160.from_bytes(issuer_bytes);
issuer.set_version(Base.VER_ACCOUNT_ID);
var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97;
var mantissa_bytes = value_bytes.slice(1);
mantissa_bytes[0] &= 0x3f;
var value = new BigInteger(mantissa_bytes, 256);
if (value.equals(BigInteger.ZERO) && !is_zero) {
throw new Error('Invalid zero representation');
}
amount._value = value;
amount._offset = offset;
amount._currency = currency;
amount._issuer = issuer;
amount._is_native = false;
} else {
//native
var integer_bytes = value_bytes.slice();
integer_bytes[0] &= 0x3f;
amount._value = new BigInteger(integer_bytes, 256);
amount._is_native = true;
}
amount._is_negative = !is_zero && !(value_bytes[0] & 0x40);
return amount;
}
});
STAmount.id = 6;
var STVL = exports.VariableLength = exports.VL = new SerializedType({
serialize: function (so, val) {
if (typeof val === 'string') {
var flag = val === '' ? true : false;
serializeHex(so, val, flag);
} else {
throw new Error('Unknown datatype.');
}
},
parse: function (so) {
var len = this.parse_varint(so);
return convertByteArrayToHex(so.read(len));
}
});
STVL.id = 7;
/*
* the input need to be Address string.
* Return a string instead of
*/
var STAccount = exports.Account = new SerializedType({
serialize: function (so, val) {
var byte_data = convertAddressToBytes(val);
SerializedType.serialize_varint(so, byte_data.length);
so.append(byte_data);
},
parse: function (so) {
var len = this.parse_varint(so);
if (len !== 20) {
throw new Error('Non-standard-length account ID');
}
var result = convertBytesToAddress(so.read(len));//UInt160.from_bytes(so.read(len));
result.set_version(Base.VER_ACCOUNT_ID);
if (false && !result.is_valid()) {
throw new Error('Invalid Account');
}
return result;
}
});
STAccount.id = 8;
var STPathSet = exports.PathSet = new SerializedType({
typeBoundary: 0xff,
typeEnd: 0x00,
typeAccount: 0x01,
typeCurrency: 0x10,
typeIssuer: 0x20,
serialize: function (so, val) {
for (var i = 0, l = val.length; i < l; i++) {
// Boundary
if (i) {
STInt8.serialize(so, this.typeBoundary);
}
for (var j = 0, l2 = val[i].length; j < l2; j++) {
var entry = val[i][j];
//if (entry.hasOwnProperty('_value')) {entry = entry._value;}
var type = 0;
if (entry.account) {
type |= this.typeAccount;
}
if (entry.currency) {
type |= this.typeCurrency;
}
if (entry.issuer) {
type |= this.typeIssuer;
}
STInt8.serialize(so, type);
if (entry.account) {
//so.append(UInt160.from_json(entry.account).to_bytes());
so.append(convertAddressToBytes(entry.account));
}
if (entry.currency) {
var currencyBytes = STCurrency.from_json_to_bytes(entry.currency, entry.non_native);
so.append(currencyBytes);
}
if (entry.issuer) {
//so.append(UInt160.from_json(entry.issuer).to_bytes());
so.append(convertAddressToBytes(entry.issuer));
}
}
}
STInt8.serialize(so, this.typeEnd);
},
parse: function (so) {
// should return a list of lists:
/*
[
[entry, entry],
[entry, entry, entry],
[entry],
[]
]
each entry has one or more of the following attributes: amount, currency, issuer.
*/
var path_list = [];
var current_path = [];
var tag_byte;
while ((tag_byte = so.read(1)[0]) !== this.typeEnd) {
//TODO: try/catch this loop, and catch when we run out of data without reaching the end of the data structure.
//Now determine: is this an end, boundary, or entry-begin-tag?
//console.log('Tag byte:', tag_byte);
if (tag_byte === this.typeBoundary) {
//console.log('Boundary');
if (current_path) { //close the current path, if there is one,
path_list.push(current_path);
}
current_path = []; //and start a new one.
continue;
}
//It's an entry-begin tag.
//console.log('It's an entry-begin tag.');
var entry = {};
if (tag_byte & this.typeAccount) {
//console.log('entry.account');
/*var bta = so.read(20);
console.log('BTA:', bta);*/
entry.account = STHash160.parse(so);
entry.account.set_version(Base.VER_ACCOUNT_ID);
}
if (tag_byte & this.typeCurrency) {
//console.log('entry.currency');
entry.currency = STCurrency.parse(so);
if (entry.currency.to_json() === 'SWT' && !entry.currency.is_native()) {
entry.non_native = true;
}
}
if (tag_byte & this.typeIssuer) {
//console.log('entry.issuer');
entry.issuer = STHash160.parse(so);
// Enable and set correct type of base-58 encoding
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
//console.log('DONE WITH ISSUER!');
}
if (entry.account || entry.currency || entry.issuer) {
current_path.push(entry);
} else {
throw new Error('Invalid path entry'); //It must have at least something in it.
}
}
if (current_path) {
//close the current path, if there is one,
path_list.push(current_path);
}
return path_list;
}
});
STPathSet.id = 18;
var STVector256 = exports.Vector256 = new SerializedType({
serialize: function (so, val) { //Assume val is an array of STHash256 objects.
var length_as_varint = SerializedType.serialize_varint(so, val.length * 32);
for (var i = 0, l = val.length; i < l; i++) {
STHash256.serialize(so, val[i]);
}
},
parse: function (so) {
var length = this.parse_varint(so);
var output = [];
// length is number of bytes not number of Hash256
for (var i = 0; i < length / 32; i++) {
output.push(STHash256.parse(so));
}
return output;
}
});
STVector256.id = 19;
// Internal
var STMemo = exports.STMemo = new SerializedType({
serialize: function (so, val, no_marker) {
var keys = [];
Object.keys(val).forEach(function (key) {
// Ignore lowercase field names - they're non-serializable fields by
// convention.
if (key[0] === key[0].toLowerCase()) {
return;
}
//Check the field
if (typeof INVERSE_FIELDS_MAP[key] === 'undefined') {
throw new Error('JSON contains unknown field: "' + key + '"');
}
keys.push(key);
});
// Sort fields
keys = sort_fields(keys);
// store that we're dealing with json
var isJson = val.MemoFormat === 'json';
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var value = val[key];
switch (key) {
// MemoType and MemoFormat are always ASCII strings
case 'MemoType':
case 'MemoFormat':
value = convertStringToHex(value);
break;
// MemoData can be a JSON object, otherwise it's a string
case 'MemoData':
if (typeof value !== 'string') {
if (isJson) {
try {
value = convertStringToHex(JSON.stringify(value));
} catch (e) {
throw new Error('MemoFormat json with invalid JSON in MemoData field');
}
} else {
throw new Error('MemoData can only be a JSON object with a valid json MemoFormat');
}
} else if (isString(value)) {
value = convertStringToHex(value);
}
break;
}
serialize(so, key, value);
}
if (!no_marker) {
//Object ending marker
STInt8.serialize(so, 0xe1);
}
},
parse: function (so) {
var output = {};
while (so.peek(1)[0] !== 0xe1) {
var keyval = parse(so);
output[keyval[0]] = keyval[1];
}
if (output['MemoType'] !== void(0)) {
output['parsed_memo_type'] = convertHexToString(output['MemoType']);
}
if (output['MemoFormat'] !== void(0)) {
output['parsed_memo_format'] = convertHexToString(output['MemoFormat']);
}
if (output['MemoData'] !== void(0)) {
// see if we can parse JSON
if (output['parsed_memo_format'] === 'json') {
try {
output['parsed_memo_data'] = JSON.parse(convertHexToString(output['MemoData']));
} catch (e) {
// fail, which is fine, we just won't add the memo_data field
}
} else if (output['parsed_memo_format'] === 'text') {
output['parsed_memo_data'] = convertHexToString(output['MemoData']);
}
}
so.read(1);
return output;
}
});
exports.serialize = exports.serialize_whatever = serialize;
/*
* return the transaction type in string
* Data defined in the TRANSACTION_TYPES
*/
function get_transaction_type(structure) {
var output;
switch (typeof structure) {
case 'number':
switch (structure) {
case 0:
output = 'Payment';
break;
case 3:
output = 'AccountSet';
break;
case 5:
output = 'SetRegularKey';
break;
case 7:
output = 'OfferCreate';
break;
case 8:
output = 'OfferCancel';
break;
case 9:
output = 'Contract';
break;
case 10:
output = 'RemoveContract';
break;
case 20:
output = 'TrustSet';
break;
case 21:
output='RelationSet';
break;
case 22:
output='RelationDel';
break;
case 30:
output='ConfigContract';
break;
case 100:
output = 'EnableFeature';
break;
case 101:
output = 'SetFee';
break;
default:
throw new Error('Invalid transaction type!');
}
break;
case 'string':
switch (structure) {
case 'Payment':
output = 0;
break;
case 'AccountSet':
output = 3;
break;
case 'SetRegularKey':
output = 5;
break;
case 'OfferCreate':
output = 7;
break;
case 'OfferCancel':
output = 8;
break;
case 'Contract':
output = 9;
break;
case 'RemoveContract':
output = 10;
break;
case 'TrustSet':
output = 20;
break;
case 'RelationSet':
output = 21;
break;
case 'RelationDel':
output = 22;
break;
case 'ConfigContract':
output = 30;
break;
case 'EnableFeature':
output = 100;
break;
case 'SetFee':
output = 101;
break;
default:
throw new Error('Invalid transaction type!');
}
break;
default:
throw new Error('Invalid input type for transaction type!');
}//end typeof structure
console.log('Get tx type:', output);
return output;
}
exports.get_transaction_type = get_transaction_type;
/*
* return the transaction result in string
* Data defined in the TRANSACTION_RESULTS
* tesSUCCESS : 0,
tecCLAIM : 100,
tecPATH_PARTIAL : 101,
tecUNFUNDED_ADD : 102,
tecUNFUNDED_OFFER : 103,
tecUNFUNDED_PAYMENT : 104,
tecFAILED_PROCESSING : 105,
tecDIR_FULL : 121,
tecINSUF_RESERVE_LINE : 122,
tecINSUF_RESERVE_OFFER : 123,
tecNO_DST : 124,
tecNO_DST_INSUF_SWT : 125,
tecNO_LINE_INSUF_RESERVE : 126,
tecNO_LINE_REDUNDANT : 127,
tecPATH_DRY : 128,
tecMASTER_DISABLED : 130,
tecNO_REGULAR_KEY : 131,
tecOWNERS : 132,
tecNO_ISSUER : 133,
tecNO_AUTH : 134,
tecNO_LINE : 135,
tecINSUFF_FEE : 136,
tecFROZEN : 137,
tecNO_TARGET : 138,
tecNO_PERMISSION : 139,
tecNO_ENTRY : 140,
tecINSUFFICIENT_RESERVE : 141
*/
function get_transaction_result(structure) {
var output;
switch (typeof structure) {
case 'number':
switch (structure) {
case 0:
output = 'tesSUCCESS';
break;
case 100:
output = 'tecCLAIM';
break;
case 101:
output = 'tecPATH_PARTIAL';
break;
case 102:
output = 'tecUNFUNDED_ADD';
break;
case 103:
output = 'tecUNFUNDED_OFFER';
break;
case 104:
output = 'tecUNFUNDED_PAYMENT';
break;
case 105:
output = 'tecFAILED_PROCESSING';
break;
case 121:
output = 'tecDIR_FULL';
break;
case 122:
output = 'tecINSUF_RESERVE_LINE';
break;
case 141:
output = 'tecINSUFFICIENT_RESERVE';
break;
default:
throw new Error('Invalid transaction result!');
}
break;
case 'string':
switch (structure) {
case 'tesSUCCESS':
output = 0;
break;
case 'tecCLAIM':
output = 100;
break;
case 'tecPATH_PARTIAL':
output = 101;
break;
case 'tecUNFUNDED_ADD':
output = 102;
break;
case 'tecUNFUNDED_OFFER':
output = 103;
break;
case 'tecUNFUNDED_PAYMENT':
output = 104;
break;
case 'tecFAILED_PROCESSING':
output = 105;
break;
case 'tecDIR_FULL':
output = 121;
break;
case 'tecINSUF_RESERVE_LINE':
output = 122;
break;
case 'tecINSUFFICIENT_RESERVE':
output = 141;
break;
default:
throw new Error('Invalid transaction result!');
}
break;
default:
throw new Error('Invalid input type for transaction result!');
}//end typeof structure
console.log('Get tx result:', output);
return output;
};
exports.get_transaction_result = get_transaction_result;
/*
* return the transaction type in string
* Data defined in the ledger entry:
AccountRoot: [97].concat(sleBase,[
Contract: [99].concat(sleBase,[
DirectoryNode: [100].concat(sleBase,[
EnabledFeatures: [102].concat(sleBase,[
FeeSettings: [115].concat(sleBase,[
GeneratorMap: [103].concat(sleBase,[
LedgerHashes: [104].concat(sleBase,[
Nickname: [110].concat(sleBase,[
Offer: [111].concat(sleBase,[
SkywellState: [114].concat(sleBase,[
TODO: add string input handles
*/
function get_ledger_entry_type(structure) {
var output;
switch (typeof structure) {
case 'number':
switch (structure) {
case 97:
output = 'AccountRoot';
break;
case 99:
output = 'Contract';
break;
case 100:
output = 'DirectoryNode';
break;
case 102:
output = 'EnabledFeatures';
break;
case 115:
output = 'FeeSettings';
break;
case 103:
output = 'GeneratorMap';
break;
case 104:
output = 'LedgerHashes';
break;
case 110:
output = 'Nickname';
break;
case 111:
output = 'Offer';
break;
case 114:
output = 'SkywellState';
break;
default:
throw new Error('Invalid input type for ransaction result!');
}
break;
case 'string':
switch (structure) {
case 'AccountRoot':
output = 97;
break;
case 'Contract':
output = 99;
break;
case 'DirectoryNode':
output = 100;
break;
case 'EnabledFeatures':
output = 102;
break;
case 'FeeSettings':
output = 115;
break;
case 'GeneratorMap':
output = 103;
break;
case 'LedgerHashes':
output = 104;
break;
case 'Nickname':
output = 110;
break;
case 'Offer':
output = 111;
break;
case 'SkywellState':
output = 114;
break;
default:
output = 0;//undefined results, should not come here.
}
break;
default:
output = 'UndefinedLedgerEntry';
}//end typeof structure
console.log('Get ledger entry type:', output);
return output;
}
exports.get_ledger_entry_type = get_ledger_entry_type;
function serialize(so, field_name, value) {
//so: a byte-stream to serialize into.
//field_name: a string for the field name ('LedgerEntryType' etc.)
//value: the value of that field.
var field_coordinates = INVERSE_FIELDS_MAP[field_name];
var type_bits = field_coordinates[0];
var field_bits = field_coordinates[1];
var tag_byte = (type_bits < 16 ? type_bits << 4 : 0) | (field_bits < 16 ? field_bits : 0);
if ('string' === typeof value) {
if (field_name === 'LedgerEntryType') {
value = get_ledger_entry_type(value);
} else if (field_name === 'TransactionResult') {
value = get_transaction_type(value);//binformat.ter[value];
}
}
STInt8.serialize(so, tag_byte);
if (type_bits >= 16) {
STInt8.serialize(so, type_bits);
}
if (field_bits >= 16) {
STInt8.serialize(so, field_bits);
}
// Get the serializer class (ST...)
var serialized_object_type;
if (field_name === 'Memo' && typeof value === 'object') {
// for Memo we override the default behavior with our STMemo serializer
serialized_object_type = exports.STMemo;
} else {
// for a field based on the type bits.
serialized_object_type = exports[TYPES_MAP[type_bits]];
}
try {
serialized_object_type.serialize(so, value);
} catch (e) {
e.message += ' (' + field_name + ')';