@openproduct/web-sdk
Version:
OpenMask Web - TypeScript Browser SDK for TON blockchain
193 lines (174 loc) • 5.29 kB
text/typescript
import {
base64toString,
bytesToHex,
crc16,
hexToBytes,
stringToBase64,
stringToBytes,
} from "./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}}
*/
export function parseFriendlyAddress(addressString: string) {
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: number | null = 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 };
}
export interface HashData {
wc: number;
hashPart: Uint8Array;
}
export class Address {
/**
* @param anyForm {string | Address}
*/
static isValid(anyForm: string | Address | any): anyForm is Address {
try {
new Address(anyForm);
return true;
} catch (e) {
return false;
}
}
wc: number;
hashPart: Uint8Array;
isTestOnly: boolean;
isUserFriendly: boolean;
isBounceable: boolean;
isUrlSafe: boolean;
/**
* @param anyForm {string | Address}
*/
constructor(anyForm: string | Address | HashData) {
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;
}
if (typeof anyForm != "string") {
this.wc = anyForm.wc;
this.hashPart = anyForm.hashPart;
this.isTestOnly = false;
this.isUserFriendly = false;
this.isBounceable = false;
this.isUrlSafe = false;
return;
}
if (anyForm.search(/\-/) > 0 || anyForm.search(/_/) > 0) {
this.isUrlSafe = true;
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?: boolean,
isUrlSafe?: boolean,
isBounceable?: boolean,
isTestOnly?: boolean
) {
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);
// TODO: CHECK
const params: any = new Uint8Array(addressWithChecksum);
let addressBase64 = stringToBase64(
String.fromCharCode.apply(null, params)
);
if (isUrlSafe) {
addressBase64 = addressBase64.replace(/\+/g, "-").replace(/\//g, "_");
}
return addressBase64;
}
}
}
export default Address;