@ton/core
Version:
Core TypeScript library that implements low level primitives for TON blockchain.
216 lines (177 loc) • 6.17 kB
text/typescript
/**
* Copyright (c) Whales Corp.
* All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import inspectSymbol from 'symbol.inspect';
import { crc16 } from '../utils/crc16';
const bounceable_tag = 0x11;
const non_bounceable_tag = 0x51;
const test_flag = 0x80;
function parseFriendlyAddress(src: string | Buffer) {
if (typeof src === 'string' && !Address.isFriendly(src)) {
throw new Error('Unknown address type');
}
const data = Buffer.isBuffer(src) ? src : Buffer.from(src, 'base64');
// 1byte tag + 1byte workchain + 32 bytes hash + 2 byte crc
if (data.length !== 36) {
throw new Error('Unknown address type: byte length is not equal to 36');
}
// Prepare data
const addr = data.subarray(0, 34);
const crc = data.subarray(34, 36);
const calcedCrc = crc16(addr);
if (!(calcedCrc[0] === crc[0] && calcedCrc[1] === crc[1])) {
throw new Error('Invalid checksum: ' + src);
}
// Parse tag
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];
}
const hashPart = addr.subarray(2, 34);
return { isTestOnly, isBounceable, workchain, hashPart };
}
export class Address {
static isAddress(src: any): src is Address {
return src instanceof Address;
}
static isFriendly(source: string) {
// Check length
if (source.length !== 48) {
return false;
}
// Check if address is valid base64
if (!/[A-Za-z0-9+/_-]+/.test(source)) {
return false;
}
return true;
}
static isRaw(source: string) {
// Check if has delimiter
if (source.indexOf(':') === -1) {
return false;
}
let [wc, hash] = source.split(':');
// wc is not valid
if (!Number.isInteger(parseFloat(wc))) {
return false;
}
// hash is not valid
if (!/[a-f0-9]+/.test(hash.toLowerCase())) {
return false;
}
// has is not correct
if (hash.length !== 64) {
return false;
}
return true;
}
static normalize(source: string | Address) {
if (typeof source === 'string') {
return Address.parse(source).toString();
} else {
return source.toString();
}
}
static parse(source: string) {
if (Address.isFriendly(source)) {
return this.parseFriendly(source).address;
} else if (Address.isRaw(source)) {
return this.parseRaw(source);
} else {
throw new Error('Unknown address type: ' + source);
}
}
static parseRaw(source: string) {
let workChain = parseInt(source.split(":")[0]);
let hash = Buffer.from(source.split(":")[1], 'hex');
return new Address(workChain, hash);
}
static parseFriendly(source: string | Buffer) {
if (Buffer.isBuffer(source)) {
let r = parseFriendlyAddress(source);
return {
isBounceable: r.isBounceable,
isTestOnly: r.isTestOnly,
address: new Address(r.workchain, r.hashPart)
};
} else {
let addr = source.replace(/\-/g, '+').replace(/_/g, '\/'); // Convert from url-friendly to true base64
let r = parseFriendlyAddress(addr);
return {
isBounceable: r.isBounceable,
isTestOnly: r.isTestOnly,
address: new Address(r.workchain, r.hashPart)
};
}
}
readonly workChain: number;
readonly hash: Buffer;
constructor(workChain: number, hash: Buffer) {
if (hash.length !== 32) {
throw new Error('Invalid address hash length: ' + hash.length);
}
this.workChain = workChain;
this.hash = hash;
Object.freeze(this);
}
toRawString = () => {
return this.workChain + ':' + this.hash.toString('hex');
}
equals(src: Address) {
if (src.workChain !== this.workChain) {
return false;
}
return src.hash.equals(this.hash);
}
toRaw = () => {
const addressWithChecksum = Buffer.alloc(36);
addressWithChecksum.set(this.hash);
addressWithChecksum.set([this.workChain, this.workChain, this.workChain, this.workChain], 32);
return addressWithChecksum;
}
toStringBuffer = (args?: { bounceable?: boolean, testOnly?: boolean }) => {
let testOnly = (args && args.testOnly !== undefined) ? args.testOnly : false;
let bounceable = (args && args.bounceable !== undefined) ? args.bounceable : true;
let tag = bounceable ? bounceable_tag : non_bounceable_tag;
if (testOnly) {
tag |= test_flag;
}
const addr = Buffer.alloc(34);
addr[0] = tag;
addr[1] = this.workChain;
addr.set(this.hash, 2);
const addressWithChecksum = Buffer.alloc(36);
addressWithChecksum.set(addr);
addressWithChecksum.set(crc16(addr), 34);
return addressWithChecksum;
}
toString = (args?: { urlSafe?: boolean, bounceable?: boolean, testOnly?: boolean }) => {
let urlSafe = (args && args.urlSafe !== undefined) ? args.urlSafe : true;
let buffer = this.toStringBuffer(args);
if (urlSafe) {
return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_');
} else {
return buffer.toString('base64');
}
}
[inspectSymbol] = () => this.toString()
}
export function address(src: string) {
return Address.parse(src);
}