@aeternity/aepp-sdk
Version:
SDK for the æternity blockchain
448 lines (431 loc) • 13.1 kB
JavaScript
import { AE_AMOUNT_FORMATS, formatAmount } from './utils/amount-formatter.js';
import { isAccountNotFoundError, pause } from './utils/other.js';
import { unwrapProxy } from './utils/wrap-proxy.js';
import { isName, produceNameId } from './tx/builder/helpers.js';
import { DRY_RUN_ACCOUNT } from './tx/builder/constants.js';
import { AensPointerContextError, DryRunError, InvalidAensNameError, TxTimedOutError, TxNotInChainError, InternalError } from './utils/errors.js';
import { decode, encode, Encoding } from './utils/encoder.js';
async function getEventInterval(type, {
_expectedMineRate,
_microBlockCycle,
onNode
}) {
if (_expectedMineRate != null && type === 'key-block') return _expectedMineRate;
if (_microBlockCycle != null && type === 'micro-block') return _microBlockCycle;
const networkId = await onNode.getNetworkId();
if (networkId === 'ae_dev') return 0;
if (!['ae_mainnet', 'ae_uat'].includes(networkId) && (await onNode._isHyperchain())) return 3000;
if (type === 'key-block') return 180000;else return 3000;
}
/**
* @category chain
* @param type - Type
* @param options - Options
*/
export async function _getPollInterval(type, options) {
return Math.floor((await getEventInterval(type, options)) / 3);
}
const heightCache = new WeakMap();
/**
* Obtain current height of the chain
* @category chain
* @param options - Options
* @param options.cached - Get height from the cache. The lag behind the actual height shouldn't
* be more than 1 block. Use if needed to reduce requests count, and approximate value can be used.
* For example, for timeout check in transaction status polling.
* @returns Current chain height
*/
export async function getHeight({
cached = false,
...options
}) {
const onNode = unwrapProxy(options.onNode);
if (cached) {
const cache = heightCache.get(onNode);
if (cache != null && cache.time > Date.now() - (await _getPollInterval('key-block', options))) {
return cache.height;
}
}
const {
height
} = await onNode.getCurrentKeyBlockHeight();
heightCache.set(onNode, {
height,
time: Date.now()
});
return height;
}
/**
* Return transaction details if it is mined, fail otherwise.
* If the transaction has ttl specified then would wait till it leaves the mempool.
* Otherwise would fail if a specified amount of blocks were mined.
* @category chain
* @param th - The hash of transaction to poll
* @param options - Options
* @param options.interval - Interval (in ms) at which to poll the chain
* @param options.blocks - Number of blocks mined after which to fail if transaction ttl is not set
* @param options.onNode - Node to use
* @returns The transaction as it was mined
*/
export async function poll(th, {
blocks = 5,
interval,
...options
}) {
interval !== null && interval !== void 0 ? interval : interval = await _getPollInterval('micro-block', options);
let max;
do {
const tx = await options.onNode.getTransactionByHash(th);
if (tx.blockHeight !== -1) return tx;
if (max == null) {
max = tx.tx.ttl !== 0 ? -1 : (await getHeight({
...options,
cached: true
})) + blocks;
}
await pause(interval);
} while (max === -1 ? true : (await getHeight({
...options,
cached: true
})) < max);
throw new TxTimedOutError(blocks, th);
}
/**
* Wait for the chain to reach a specific height
* @category chain
* @param height - Height to wait for
* @param options - Options
* @param options.interval - Interval (in ms) at which to poll the chain
* @param options.onNode - Node to use
* @returns Current chain height
*/
export async function awaitHeight(height, {
interval,
...options
}) {
interval !== null && interval !== void 0 ? interval : interval = Math.min(await _getPollInterval('key-block', options), 5000);
let currentHeight;
do {
if (currentHeight != null) await pause(interval);
currentHeight = await getHeight(options);
} while (currentHeight < height);
return currentHeight;
}
/**
* Wait for transaction confirmation
* @category chain
* @param txHash - Transaction hash
* @param options - Options
* @param options.confirm - Number of micro blocks to wait for transaction confirmation
* @param options.onNode - Node to use
* @returns Current Height
*/
export async function waitForTxConfirm(txHash, {
confirm = 3,
onNode,
...options
}) {
const {
blockHeight
} = await onNode.getTransactionByHash(txHash);
const height = await awaitHeight(blockHeight + confirm, {
onNode,
...options
});
const {
blockHeight: newBlockHeight
} = await onNode.getTransactionByHash(txHash);
switch (newBlockHeight) {
case -1:
throw new TxNotInChainError(txHash);
case blockHeight:
return height;
default:
return waitForTxConfirm(txHash, {
onNode,
confirm,
...options
});
}
}
/**
* Get account by account public key
* @category chain
* @param address - Account address (public key)
* @param options - Options
* @param options.height - Get account on specific block by block height
* @param options.hash - Get account on specific block by micro block hash or key block hash
* @param options.onNode - Node to use
*/
export async function getAccount(address, {
height,
hash,
onNode
}) {
if (height != null) return onNode.getAccountByPubkeyAndHeight(address, height);
if (hash != null) return onNode.getAccountByPubkeyAndHash(address, hash);
return onNode.getAccountByPubkey(address);
}
/**
* Request the balance of specified account
* @category chain
* @param address - The public account address to obtain the balance for
* @param options - Options
* @param options.format
* @param options.height - The chain height at which to obtain the balance for
* (default: top of chain)
* @param options.hash - The block hash on which to obtain the balance for (default: top of chain)
*/
export async function getBalance(address, {
/**
* @deprecated no replacement implemented yet
*/
format = AE_AMOUNT_FORMATS.AETTOS,
...options
}) {
const addr = address.startsWith('ok_') ? encode(decode(address), Encoding.AccountAddress) : address;
const {
balance
} = await getAccount(addr, options).catch(error => {
if (!isAccountNotFoundError(error)) throw error;
return {
balance: 0n
};
});
return formatAmount(balance, {
targetDenomination: format
});
}
/**
* Obtain current generation
* @category chain
* @param options - Options
* @param options.onNode - Node to use
* @returns Current Generation
* @deprecated Use {@link Node.getCurrentGeneration} instead
*/
export async function getCurrentGeneration({
onNode
}) {
return onNode.getCurrentGeneration();
}
/**
* Get generation by hash or height
* @category chain
* @param hashOrHeight - Generation hash or height
* @param options - Options
* @param options.onNode - Node to use
* @returns Generation
* @deprecated Use {@link Node.getGenerationByHash} or {@link Node.getGenerationByHeight} instead
*/
export async function getGeneration(hashOrHeight, {
onNode
}) {
if (typeof hashOrHeight === 'number') return onNode.getGenerationByHeight(hashOrHeight);
return onNode.getGenerationByHash(hashOrHeight);
}
/**
* Get micro block transactions
* @category chain
* @param hash - Micro block hash
* @param options - Options
* @param options.onNode - Node to use
* @returns Transactions
* @deprecated Use {@link Node.getMicroBlockTransactionsByHash} instead
*/
export async function getMicroBlockTransactions(hash, {
onNode
}) {
return (await onNode.getMicroBlockTransactionsByHash(hash)).transactions;
}
/**
* Get key block
* @category chain
* @param hashOrHeight - Key block hash or height
* @param options - Options
* @param options.onNode - Node to use
* @returns Key Block
* @deprecated Use {@link Node.getKeyBlockByHeight} or {@link Node.getKeyBlockByHash} instead
*/
export async function getKeyBlock(hashOrHeight, {
onNode
}) {
if (typeof hashOrHeight === 'number') return onNode.getKeyBlockByHeight(hashOrHeight);
return onNode.getKeyBlockByHash(hashOrHeight);
}
/**
* Get micro block header
* @category chain
* @param hash - Micro block hash
* @param options - Options
* @param options.onNode - Node to use
* @returns Micro block header
* @deprecated Use {@link Node.getMicroBlockHeaderByHash} instead
*/
export async function getMicroBlockHeader(hash, {
onNode
}) {
return onNode.getMicroBlockHeaderByHash(hash);
}
const txDryRunRequests = new Map();
async function txDryRunHandler(key, onNode) {
const rs = txDryRunRequests.get(key);
txDryRunRequests.delete(key);
if (rs == null) throw new InternalError("Can't get dry-run request");
let dryRunRes;
try {
const top = typeof rs[0].top === 'number' ? (await onNode.getKeyBlockByHeight(rs[0].top)).hash : rs[0].top;
dryRunRes = await onNode.protectedDryRunTxs({
top,
txEvents: rs[0].txEvents,
txs: rs.map(req => ({
tx: req.tx
})),
accounts: Array.from(new Set(rs.map(req => req.accountAddress))).map(pubKey => ({
pubKey,
amount: DRY_RUN_ACCOUNT.amount
}))
});
} catch (error) {
rs.forEach(({
reject
}) => reject(error));
return;
}
const {
results,
txEvents
} = dryRunRes;
results.forEach(({
result,
reason,
...resultPayload
}, idx) => {
const {
resolve,
reject,
tx,
accountAddress
} = rs[idx];
if (result === 'ok') resolve({
...resultPayload,
txEvents
});else reject(Object.assign(new DryRunError(reason), {
tx,
accountAddress
}));
});
}
/**
* Transaction dry-run
* @category chain
* @param tx - transaction to execute
* @param accountAddress - address that will be used to execute transaction
* @param options - Options
* @param options.top - hash of block on which to make dry-run
* @param options.txEvents - collect and return on-chain tx events that would result from the call
* @param options.combine - Enables combining of similar requests to a single dry-run call
* @param options.onNode - Node to use
*/
export async function txDryRun(tx, accountAddress, {
top,
txEvents,
combine,
onNode
}) {
var _txDryRunRequests$get;
const key = combine === true ? [top, txEvents].join() : 'immediate';
const requests = (_txDryRunRequests$get = txDryRunRequests.get(key)) !== null && _txDryRunRequests$get !== void 0 ? _txDryRunRequests$get : [];
txDryRunRequests.set(key, requests);
return new Promise((resolve, reject) => {
var _requests$timeout;
requests.push({
tx,
accountAddress,
top,
txEvents,
resolve,
reject
});
if (combine !== true) {
void txDryRunHandler(key, onNode);
return;
}
(_requests$timeout = requests.timeout) !== null && _requests$timeout !== void 0 ? _requests$timeout : requests.timeout = setTimeout(() => {
void txDryRunHandler(key, onNode);
});
});
}
/**
* Get contract byte code
* @category contract
* @param contractId - Contract address
* @param options - Options
* @param options.onNode - Node to use
* @deprecated Use {@link Node.getContractCode} instead
*/
export async function getContractByteCode(contractId, {
onNode
}) {
return onNode.getContractCode(contractId);
}
/**
* Get contract entry
* @category contract
* @param contractId - Contract address
* @param options - Options
* @param options.onNode - Node to use
* @deprecated Use {@link Node.getContract} instead
*/
export async function getContract(contractId, {
onNode
}) {
return onNode.getContract(contractId);
}
/**
* Get name entry
* @category AENS
* @param name - AENS name
* @param options - Options
* @param options.onNode - Node to use
* @deprecated Use {@link Node.getNameEntryByName} or {@link Name.getState} instead
*/
export async function getName(name, {
onNode
}) {
return onNode.getNameEntryByName(name);
}
/**
* Resolve AENS name and return name hash
* @category AENS
* @param nameOrId - AENS name or address
* @param key - in AENS pointers record
* @param options - Options
* @param options.verify - To ensure that name exist and have a corresponding pointer
* // TODO: avoid that to don't trust to current api gateway
* @param options.resolveByNode - Enables pointer resolving using node
* @param options.onNode - Node to use
* @returns Address or AENS name hash
*/
export async function resolveName(nameOrId, key, {
verify = true,
resolveByNode = false,
onNode
}) {
if (isName(nameOrId)) {
if (verify || resolveByNode) {
const name = await onNode.getNameEntryByName(nameOrId);
const pointer = name.pointers.find(p => p.key === key);
if (pointer == null) throw new AensPointerContextError(nameOrId, key);
if (resolveByNode) return pointer.id;
}
return produceNameId(nameOrId);
}
try {
decode(nameOrId);
return nameOrId;
} catch (error) {
throw new InvalidAensNameError(`Invalid name or address: ${nameOrId}`);
}
}
//# sourceMappingURL=chain.js.map