tonweb
Version:
TonWeb - JavaScript API for TON blockchain
154 lines (136 loc) • 5.09 kB
JavaScript
const {crc16, hexToBytes, bytesToHex, stringToBytes, base64toString, stringToBase64} = require("./Utils");
const bounceable_tag = 0x11;
const non_bounceable_tag = 0x51;
const test_flag = 0x80;
/**
* @private
* @param addressString {string}
* @return {{isTestOnly: boolean, workchain: number, hashPart: Uint8Array, isBounceable: boolean}}
*/
function parseFriendlyAddress(addressString) {
if (addressString.length !== 48) {
throw new Error(`User-friendly address should contain strictly 48 characters`);
}
const data = stringToBytes(base64toString(addressString));
if (data.length !== 36) { // 1byte tag + 1byte workchain + 32 bytes hash + 2 byte crc
throw "Unknown address type: byte length is not equal to 36";
}
const addr = data.slice(0, 34);
const crc = data.slice(34, 36);
const calcedCrc = crc16(addr);
if (!(calcedCrc[0] === crc[0] && calcedCrc[1] === crc[1])) {
throw "Wrong crc16 hashsum";
}
let tag = addr[0];
let isTestOnly = false;
let isBounceable = false;
if (tag & test_flag) {
isTestOnly = true;
tag = tag ^ test_flag;
}
if ((tag !== bounceable_tag) && (tag !== non_bounceable_tag))
throw "Unknown address tag";
isBounceable = tag === bounceable_tag;
let workchain = null;
if (addr[1] === 0xff) { // TODO we should read signed integer here
workchain = -1;
} else {
workchain = addr[1];
}
if (workchain !== 0 && workchain !== -1) throw new Error('Invalid address wc ' + workchain);
const hashPart = addr.slice(2, 34);
return {isTestOnly, isBounceable, workchain, hashPart};
}
class Address {
/**
* @param anyForm {string | Address}
*/
static isValid(anyForm) {
try {
new Address(anyForm);
return true;
} catch (e) {
return false;
}
}
/**
* @param anyForm {string | Address}
*/
constructor(anyForm) {
if (anyForm == null) {
throw "Invalid address";
}
if (anyForm instanceof Address) {
this.wc = anyForm.wc;
this.hashPart = anyForm.hashPart;
this.isTestOnly = anyForm.isTestOnly;
this.isUserFriendly = anyForm.isUserFriendly;
this.isBounceable = anyForm.isBounceable;
this.isUrlSafe = anyForm.isUrlSafe;
return;
}
this.isUrlSafe = true;
if (anyForm.search(/\-/) > 0 || anyForm.search(/_/) > 0) {
anyForm = anyForm.replace(/\-/g, '+').replace(/_/g, '\/');
} else {
this.isUrlSafe = false;
}
if (anyForm.indexOf(':') > -1) {
const arr = anyForm.split(':');
if (arr.length !== 2) throw new Error('Invalid address ' + anyForm);
const wc = parseInt(arr[0]);
if (wc !== 0 && wc !== -1) throw new Error('Invalid address wc ' + anyForm);
const hex = arr[1];
if (hex.length !== 64) throw new Error('Invalid address hex ' + anyForm);
this.isUserFriendly = false;
this.wc = wc;
this.hashPart = hexToBytes(hex);
this.isTestOnly = false;
this.isBounceable = false;
} else {
this.isUserFriendly = true;
const parseResult = parseFriendlyAddress(anyForm);
this.wc = parseResult.workchain;
this.hashPart = parseResult.hashPart;
this.isTestOnly = parseResult.isTestOnly;
this.isBounceable = parseResult.isBounceable;
}
}
/**
* @param isUserFriendly? {boolean}
* @param isUrlSafe? {boolean}
* @param isBounceable? {boolean}
* @param isTestOnly? {boolean}
* @return {string}
*/
toString(isUserFriendly,
isUrlSafe,
isBounceable,
isTestOnly) {
if (isUserFriendly === undefined) isUserFriendly = this.isUserFriendly;
if (isUrlSafe === undefined) isUrlSafe = this.isUrlSafe;
if (isBounceable === undefined) isBounceable = this.isBounceable;
if (isTestOnly === undefined) isTestOnly = this.isTestOnly;
if (!isUserFriendly) {
return this.wc + ":" + bytesToHex(this.hashPart);
} else {
let tag = isBounceable ? bounceable_tag : non_bounceable_tag;
if (isTestOnly) {
tag |= test_flag;
}
const addr = new Int8Array(34);
addr[0] = tag;
addr[1] = this.wc;
addr.set(this.hashPart, 2);
const addressWithChecksum = new Uint8Array(36);
addressWithChecksum.set(addr);
addressWithChecksum.set(crc16(addr), 34);
let addressBase64 = stringToBase64(String.fromCharCode.apply(null, new Uint8Array(addressWithChecksum)));
if (isUrlSafe) {
addressBase64 = addressBase64.replace(/\+/g, '-').replace(/\//g, '_');
}
return addressBase64;
}
}
}
module.exports.default = Address;