@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
215 lines (202 loc) • 7.91 kB
JavaScript
import { Buffer as _Buffer } from "buffer";
import BigNumber from 'bignumber.js';
import { genSalt, hash } from '../../utils/crypto.js';
import { decode, encode, Encoding } from '../../utils/encoder.js';
import { toBytes } from '../../utils/bytes.js';
import { concatBuffers } from '../../utils/other.js';
import { NAME_BID_RANGES, NAME_FEE_BID_INCREMENT, NAME_MAX_LENGTH_FEE } from './constants.js';
import { ceil } from '../../utils/bignumber.js';
import { ArgumentError, IllegalBidFeeError } from '../../utils/errors.js';
/**
* JavaScript-based Transaction builder helper function's
*/
/**
* Build a contract address
* @category contract
* @param owner - Address of contract owner
* @param nonce - Nonce of ContractCreateTx or state channel round when contract was created
* @returns Contract address
*/
export function buildContractId(owner, nonce) {
const ownerIdAndNonce = _Buffer.from([...decode(owner), ...toBytes(nonce)]);
const b2bHash = hash(ownerIdAndNonce);
return encode(b2bHash, Encoding.ContractAddress);
}
// TODO: add `build` prefix the same as others
/**
* Build a oracle query id
* @category oracle
* @param senderId - The public key of the sender account
* @param nonce - the nonce of the transaction
* @param oracleId - The oracle public key
* @returns Contract public key
*/
export function oracleQueryId(senderId, nonce, oracleId) {
function _int32(val) {
const nonceBE = toBytes(val, true);
return concatBuffers([_Buffer.alloc(32 - nonceBE.length), nonceBE]);
}
const b2bHash = hash(_Buffer.from([...decode(senderId), ..._int32(nonce), ...decode(oracleId)]));
return encode(b2bHash, Encoding.OracleQueryId);
}
const AENS_SUFFIX = '.chain';
export function nameToPunycode(maybeName) {
const [name, suffix, ...other] = maybeName.split('.');
if (other.length !== 0) throw new ArgumentError('aens name', 'including only one dot', maybeName);
if (suffix !== AENS_SUFFIX.slice(1)) {
throw new ArgumentError('aens name', `suffixed with ${AENS_SUFFIX}`, maybeName);
}
if (/\p{Emoji_Presentation}/u.test(name)) {
throw new ArgumentError('aens name', 'not containing emoji', maybeName);
}
if (name[2] === '-' && name[3] === '-') {
throw new ArgumentError('aens name', 'without "-" char in both the third and fourth positions', maybeName);
}
if (name[0] === '-') {
throw new ArgumentError('aens name', 'starting with no "-" char', maybeName);
}
if (name.at(-1) === '-') {
throw new ArgumentError('aens name', 'ending with no "-" char', maybeName);
}
let punycode;
try {
const u = new URL(`http://${name}.${suffix}`);
if (u.username + u.password + u.port + u.search + u.hash !== '' || u.pathname !== '/') {
throw new ArgumentError('aens name', 'valid', maybeName);
}
punycode = u.host;
} catch (error) {
if (error instanceof TypeError && error.message.includes('Invalid URL')) {
throw new ArgumentError('aens name', 'valid', maybeName);
}
throw error;
}
if (!/^[a-z0-9.-]+$/i.test(punycode)) {
throw new ArgumentError('aens name', 'without illegal chars', maybeName);
}
if (punycode.length > 63 + AENS_SUFFIX.length) {
throw new ArgumentError('aens name', 'not too long', maybeName);
}
return punycode;
}
// TODO: replace `produce` with `build` the same as others
/**
* Encode an AENS name
* @category AENS
* @param name - Name to encode
* @returns `nm_` prefixed encoded AENS name
*/
export function produceNameId(name) {
return encode(hash(nameToPunycode(name)), Encoding.Name);
}
// TODO: add `build` the same as others
/**
* Generate the commitment hash by hashing the salt and
* name, base 58 encoding the result and prepending 'cm_'
* @category transaction builder
* @param name - Name to be registered
* @param salt - Random number
* @returns Commitment hash
*/
export function commitmentHash(name, salt = genSalt()) {
return encode(hash(concatBuffers([_Buffer.from(nameToPunycode(name)), _Buffer.from(salt.toString(16).padStart(64, '0'), 'hex')])), Encoding.Commitment);
}
/**
* Utility function to convert bytes to int
* @category utils
* @param buffer - Value
* @returns Buffer Buffer from number(BigEndian)
* @deprecated use `BigInt('0x' + <buffer>.toString('hex')).toString()` instead
*/
export function readInt(buffer = _Buffer.from([])) {
return BigInt('0x' + _Buffer.from(buffer).toString('hex')).toString();
}
/**
* Ensure that name is valid AENS name, would throw an exception otherwise
* @category AENS
* @param maybeName - AENS name
*/
export function ensureName(maybeName) {
nameToPunycode(maybeName);
}
/**
* Is AENS name valid
* @category AENS
* @param maybeName - AENS name
*/
export function isName(maybeName) {
try {
ensureName(maybeName);
return true;
} catch (error) {
return false;
}
}
const encodingToPointerKey = [[Encoding.AccountAddress, 'account_pubkey'], [Encoding.OracleAddress, 'oracle_pubkey'], [Encoding.ContractAddress, 'contract_pubkey'], [Encoding.Channel, 'channel']];
/**
* @category AENS
* @param identifier - account/oracle/contract address, or channel
* @returns default AENS pointer key
*/
export function getDefaultPointerKey(identifier) {
decode(identifier);
const encoding = identifier.substring(0, 2);
const result = encodingToPointerKey.find(([e]) => e === encoding)?.[1];
if (result != null) return result;
throw new ArgumentError('identifier', `prefixed with one of ${encodingToPointerKey.map(([e]) => `${e}_`).join(', ')}`, identifier);
}
/**
* Get the minimum AENS name fee
* @category AENS
* @param name - the AENS name to get the fee for
* @returns the minimum fee for the AENS name auction
*/
export function getMinimumNameFee(name) {
const nameLength = nameToPunycode(name).length - AENS_SUFFIX.length;
return NAME_BID_RANGES[Math.min(nameLength, NAME_MAX_LENGTH_FEE)];
}
/**
* Compute bid fee for AENS auction
* @category AENS
* @param name - the AENS name to get the fee for
* @param options - Options
* @param options.startFee - Auction start fee
* @param options.increment - Bid multiplier(In percentage, must be between 0 and 1)
* @returns Bid fee
*/
export function computeBidFee(name, {
startFee,
increment = NAME_FEE_BID_INCREMENT
} = {}) {
if (!(Number(increment) === increment && increment % 1 !== 0)) throw new IllegalBidFeeError(`Increment must be float. Current increment ${increment}`);
if (increment < NAME_FEE_BID_INCREMENT) throw new IllegalBidFeeError(`minimum increment percentage is ${NAME_FEE_BID_INCREMENT}`);
// FIXME: increment should be used somehow here
return ceil(new BigNumber(startFee !== null && startFee !== void 0 ? startFee : getMinimumNameFee(name)).times(new BigNumber(NAME_FEE_BID_INCREMENT).plus(1)));
}
/**
* Compute approximate auction end height.
*
* From Ceres, each time a new (successful!) bid is made for a name the auction is extended for up
* to 120 key-blocks/generations. I.e. after the bid there is always at least 120 generations to
* make a higher bid.
*
* @category AENS
* @param name - Name to compute auction end for
* @param claimHeight - Auction starting height
* @see {@link https://github.com/aeternity/protocol/blob/cfb19ce/AENS.md#from-ceres-protocol-upgrade}
* @returns Auction end height
*/
export function computeAuctionEndBlock(name, claimHeight) {
var _ref, _ref2, _ref3;
const length = nameToPunycode(name).length - AENS_SUFFIX.length;
const h = (_ref = (_ref2 = (_ref3 = length <= 4 ? 2400 : null) !== null && _ref3 !== void 0 ? _ref3 : length <= 8 ? 960 : null) !== null && _ref2 !== void 0 ? _ref2 : length <= 12 ? 480 : null) !== null && _ref !== void 0 ? _ref : 0;
return h + claimHeight;
}
/**
* Is name accept going to auction
* @category AENS
*/
export function isAuctionName(name) {
return nameToPunycode(name).length < 13 + AENS_SUFFIX.length;
}
//# sourceMappingURL=helpers.js.map