PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/algosdk/dist/cjs/src
Просмотр файла: composer.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AtomicTransactionComposer = exports.AtomicTransactionComposerStatus = void 0;
const abi_1 = require("./abi");
const transaction_1 = require("./transaction");
const makeTxn_1 = require("./makeTxn");
const group_1 = require("./group");
const wait_1 = require("./wait");
const signer_1 = require("./signer");
const base_1 = require("./types/transactions/base");
// First 4 bytes of SHA-512/256 hash of "return"
const RETURN_PREFIX = Buffer.from([21, 31, 124, 117]);
// The maximum number of arguments for an application call transaction
const MAX_APP_ARGS = 16;
var AtomicTransactionComposerStatus;
(function (AtomicTransactionComposerStatus) {
/** The atomic group is still under construction. */
AtomicTransactionComposerStatus[AtomicTransactionComposerStatus["BUILDING"] = 0] = "BUILDING";
/** The atomic group has been finalized, but not yet signed. */
AtomicTransactionComposerStatus[AtomicTransactionComposerStatus["BUILT"] = 1] = "BUILT";
/** The atomic group has been finalized and signed, but not yet submitted to the network. */
AtomicTransactionComposerStatus[AtomicTransactionComposerStatus["SIGNED"] = 2] = "SIGNED";
/** The atomic group has been finalized, signed, and submitted to the network. */
AtomicTransactionComposerStatus[AtomicTransactionComposerStatus["SUBMITTED"] = 3] = "SUBMITTED";
/** The atomic group has been finalized, signed, submitted, and successfully committed to a block. */
AtomicTransactionComposerStatus[AtomicTransactionComposerStatus["COMMITTED"] = 4] = "COMMITTED";
})(AtomicTransactionComposerStatus = exports.AtomicTransactionComposerStatus || (exports.AtomicTransactionComposerStatus = {}));
/**
* Add a value to an application call's foreign array. The addition will be as compact as possible,
* and this function will return an index that can be used to reference `valueToAdd` in `array`.
*
* @param valueToAdd - The value to add to the array. If this value is already present in the array,
* it will not be added again. Instead, the existing index will be returned.
* @param array - The existing foreign array. This input may be modified to append `valueToAdd`.
* @param zeroValue - If provided, this value indicated two things: the 0 value is special for this
* array, so all indexes into `array` must start at 1; additionally, if `valueToAdd` equals
* `zeroValue`, then `valueToAdd` will not be added to the array, and instead the 0 indexes will
* be returned.
* @returns An index that can be used to reference `valueToAdd` in `array`.
*/
function populateForeignArray(valueToAdd, array, zeroValue) {
if (zeroValue != null && valueToAdd === zeroValue) {
return 0;
}
const offset = zeroValue == null ? 0 : 1;
for (let i = 0; i < array.length; i++) {
if (valueToAdd === array[i]) {
return i + offset;
}
}
array.push(valueToAdd);
return array.length - 1 + offset;
}
/** A class used to construct and execute atomic transaction groups */
class AtomicTransactionComposer {
constructor() {
this.status = AtomicTransactionComposerStatus.BUILDING;
this.transactions = [];
this.methodCalls = new Map();
this.signedTxns = [];
this.txIDs = [];
}
/**
* Get the status of this composer's transaction group.
*/
getStatus() {
return this.status;
}
/**
* Get the number of transactions currently in this atomic group.
*/
count() {
return this.transactions.length;
}
/**
* Create a new composer with the same underlying transactions. The new composer's status will be
* BUILDING, so additional transactions may be added to it.
*/
clone() {
const theClone = new AtomicTransactionComposer();
theClone.transactions = this.transactions.map(({ txn, signer }) => ({
// not quite a deep copy, but good enough for our purposes (modifying txn.group in buildGroup)
txn: transaction_1.Transaction.from_obj_for_encoding({
...txn.get_obj_for_encoding(),
// erase the group ID
grp: undefined,
}),
signer,
}));
theClone.methodCalls = new Map(this.methodCalls);
return theClone;
}
/**
* Add a transaction to this atomic group.
*
* An error will be thrown if the transaction has a nonzero group ID, the composer's status is
* not BUILDING, or if adding this transaction causes the current group to exceed MAX_GROUP_SIZE.
*/
addTransaction(txnAndSigner) {
if (this.status !== AtomicTransactionComposerStatus.BUILDING) {
throw new Error('Cannot add transactions when composer status is not BUILDING');
}
if (this.transactions.length === AtomicTransactionComposer.MAX_GROUP_SIZE) {
throw new Error(`Adding an additional transaction exceeds the maximum atomic group size of ${AtomicTransactionComposer.MAX_GROUP_SIZE}`);
}
if (txnAndSigner.txn.group && txnAndSigner.txn.group.some((v) => v !== 0)) {
throw new Error('Cannot add a transaction with nonzero group ID');
}
this.transactions.push(txnAndSigner);
}
/**
* Add a smart contract method call to this atomic group.
*
* An error will be thrown if the composer's status is not BUILDING, if adding this transaction
* causes the current group to exceed MAX_GROUP_SIZE, or if the provided arguments are invalid
* for the given method.
*/
addMethodCall({ appID, method, methodArgs, sender, suggestedParams, onComplete, approvalProgram, clearProgram, numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlices, extraPages, boxes, note, lease, rekeyTo, signer, }) {
if (this.status !== AtomicTransactionComposerStatus.BUILDING) {
throw new Error('Cannot add transactions when composer status is not BUILDING');
}
if (this.transactions.length + method.txnCount() >
AtomicTransactionComposer.MAX_GROUP_SIZE) {
throw new Error(`Adding additional transactions exceeds the maximum atomic group size of ${AtomicTransactionComposer.MAX_GROUP_SIZE}`);
}
if (appID === 0) {
if (approvalProgram == null ||
clearProgram == null ||
numGlobalInts == null ||
numGlobalByteSlices == null ||
numLocalInts == null ||
numLocalByteSlices == null) {
throw new Error('One of the following required parameters for application creation is missing: approvalProgram, clearProgram, numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlices');
}
}
else if (onComplete === base_1.OnApplicationComplete.UpdateApplicationOC) {
if (approvalProgram == null || clearProgram == null) {
throw new Error('One of the following required parameters for OnApplicationComplete.UpdateApplicationOC is missing: approvalProgram, clearProgram');
}
if (numGlobalInts != null ||
numGlobalByteSlices != null ||
numLocalInts != null ||
numLocalByteSlices != null ||
extraPages != null) {
throw new Error('One of the following application creation parameters were set on a non-creation call: numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlices, extraPages');
}
}
else if (approvalProgram != null ||
clearProgram != null ||
numGlobalInts != null ||
numGlobalByteSlices != null ||
numLocalInts != null ||
numLocalByteSlices != null ||
extraPages != null) {
throw new Error('One of the following application creation parameters were set on a non-creation call: approvalProgram, clearProgram, numGlobalInts, numGlobalByteSlices, numLocalInts, numLocalByteSlices, extraPages');
}
if (methodArgs == null) {
// eslint-disable-next-line no-param-reassign
methodArgs = [];
}
if (methodArgs.length !== method.args.length) {
throw new Error(`Incorrect number of method arguments. Expected ${method.args.length}, got ${methodArgs.length}`);
}
let basicArgTypes = [];
let basicArgValues = [];
const txnArgs = [];
const refArgTypes = [];
const refArgValues = [];
const refArgIndexToBasicArgIndex = new Map();
// TODO: Box encoding for ABI
const boxReferences = !boxes ? [] : boxes;
for (let i = 0; i < methodArgs.length; i++) {
let argType = method.args[i].type;
const argValue = methodArgs[i];
if ((0, abi_1.abiTypeIsTransaction)(argType)) {
if (!(0, signer_1.isTransactionWithSigner)(argValue) ||
!(0, abi_1.abiCheckTransactionType)(argType, argValue.txn)) {
throw new Error(`Expected ${argType} transaction for argument at index ${i}`);
}
if (argValue.txn.group && argValue.txn.group.some((v) => v !== 0)) {
throw new Error('Cannot add a transaction with nonzero group ID');
}
txnArgs.push(argValue);
continue;
}
if ((0, signer_1.isTransactionWithSigner)(argValue)) {
throw new Error(`Expected non-transaction value for argument at index ${i}`);
}
if ((0, abi_1.abiTypeIsReference)(argType)) {
refArgIndexToBasicArgIndex.set(refArgTypes.length, basicArgTypes.length);
refArgTypes.push(argType);
refArgValues.push(argValue);
// treat the reference as a uint8 for encoding purposes
argType = new abi_1.ABIUintType(8);
}
if (typeof argType === 'string') {
throw new Error(`Unknown ABI type: ${argType}`);
}
basicArgTypes.push(argType);
basicArgValues.push(argValue);
}
const resolvedRefIndexes = [];
const foreignAccounts = [];
const foreignApps = [];
const foreignAssets = [];
for (let i = 0; i < refArgTypes.length; i++) {
const refType = refArgTypes[i];
const refValue = refArgValues[i];
let resolved = 0;
switch (refType) {
case abi_1.ABIReferenceType.account: {
const addressType = new abi_1.ABIAddressType();
const address = addressType.decode(addressType.encode(refValue));
resolved = populateForeignArray(address, foreignAccounts, sender);
break;
}
case abi_1.ABIReferenceType.application: {
const uint64Type = new abi_1.ABIUintType(64);
const refAppID = uint64Type.decode(uint64Type.encode(refValue));
if (refAppID > Number.MAX_SAFE_INTEGER) {
throw new Error(`Expected safe integer for application value, got ${refAppID}`);
}
resolved = populateForeignArray(Number(refAppID), foreignApps, appID);
break;
}
case abi_1.ABIReferenceType.asset: {
const uint64Type = new abi_1.ABIUintType(64);
const refAssetID = uint64Type.decode(uint64Type.encode(refValue));
if (refAssetID > Number.MAX_SAFE_INTEGER) {
throw new Error(`Expected safe integer for asset value, got ${refAssetID}`);
}
resolved = populateForeignArray(Number(refAssetID), foreignAssets);
break;
}
default:
throw new Error(`Unknown reference type: ${refType}`);
}
resolvedRefIndexes.push(resolved);
}
for (let i = 0; i < resolvedRefIndexes.length; i++) {
const basicArgIndex = refArgIndexToBasicArgIndex.get(i);
basicArgValues[basicArgIndex] = resolvedRefIndexes[i];
}
if (basicArgTypes.length > MAX_APP_ARGS - 1) {
const lastArgTupleTypes = basicArgTypes.slice(MAX_APP_ARGS - 2);
const lastArgTupleValues = basicArgValues.slice(MAX_APP_ARGS - 2);
basicArgTypes = basicArgTypes.slice(0, MAX_APP_ARGS - 2);
basicArgValues = basicArgValues.slice(0, MAX_APP_ARGS - 2);
basicArgTypes.push(new abi_1.ABITupleType(lastArgTupleTypes));
basicArgValues.push(lastArgTupleValues);
}
const appArgsEncoded = [method.getSelector()];
for (let i = 0; i < basicArgTypes.length; i++) {
appArgsEncoded.push(basicArgTypes[i].encode(basicArgValues[i]));
}
const appCall = {
txn: (0, makeTxn_1.makeApplicationCallTxnFromObject)({
from: sender,
appIndex: appID,
appArgs: appArgsEncoded,
accounts: foreignAccounts,
foreignApps,
foreignAssets,
boxes: boxReferences,
onComplete: onComplete == null ? base_1.OnApplicationComplete.NoOpOC : onComplete,
approvalProgram,
clearProgram,
numGlobalInts,
numGlobalByteSlices,
numLocalInts,
numLocalByteSlices,
extraPages,
lease,
note,
rekeyTo,
suggestedParams,
}),
signer,
};
this.transactions.push(...txnArgs, appCall);
this.methodCalls.set(this.transactions.length - 1, method);
}
/**
* Finalize the transaction group and returned the finalized transactions.
*
* The composer's status will be at least BUILT after executing this method.
*/
buildGroup() {
if (this.status === AtomicTransactionComposerStatus.BUILDING) {
if (this.transactions.length === 0) {
throw new Error('Cannot build a group with 0 transactions');
}
if (this.transactions.length > 1) {
(0, group_1.assignGroupID)(this.transactions.map((txnWithSigner) => txnWithSigner.txn));
}
this.status = AtomicTransactionComposerStatus.BUILT;
}
return this.transactions;
}
/**
* Obtain signatures for each transaction in this group. If signatures have already been obtained,
* this method will return cached versions of the signatures.
*
* The composer's status will be at least SIGNED after executing this method.
*
* An error will be thrown if signing any of the transactions fails.
*
* @returns A promise that resolves to an array of signed transactions.
*/
async gatherSignatures() {
if (this.status >= AtomicTransactionComposerStatus.SIGNED) {
return this.signedTxns;
}
// retrieve built transactions and verify status is BUILT
const txnsWithSigners = this.buildGroup();
const txnGroup = txnsWithSigners.map((txnWithSigner) => txnWithSigner.txn);
const indexesPerSigner = new Map();
for (let i = 0; i < txnsWithSigners.length; i++) {
const { signer } = txnsWithSigners[i];
if (!indexesPerSigner.has(signer)) {
indexesPerSigner.set(signer, []);
}
indexesPerSigner.get(signer).push(i);
}
const orderedSigners = Array.from(indexesPerSigner);
const batchedSigs = await Promise.all(orderedSigners.map(([signer, indexes]) => signer(txnGroup, indexes)));
const signedTxns = txnsWithSigners.map(() => null);
for (let signerIndex = 0; signerIndex < orderedSigners.length; signerIndex++) {
const indexes = orderedSigners[signerIndex][1];
const sigs = batchedSigs[signerIndex];
for (let i = 0; i < indexes.length; i++) {
signedTxns[indexes[i]] = sigs[i];
}
}
if (!signedTxns.every((sig) => sig != null)) {
throw new Error(`Missing signatures. Got ${signedTxns}`);
}
const txIDs = signedTxns.map((stxn, index) => {
try {
return (0, transaction_1.decodeSignedTransaction)(stxn).txn.txID();
}
catch (err) {
throw new Error(`Cannot decode signed transaction at index ${index}. ${err}`);
}
});
this.signedTxns = signedTxns;
this.txIDs = txIDs;
this.status = AtomicTransactionComposerStatus.SIGNED;
return signedTxns;
}
/**
* Send the transaction group to the network, but don't wait for it to be committed to a block. An
* error will be thrown if submission fails.
*
* The composer's status must be SUBMITTED or lower before calling this method. If submission is
* successful, this composer's status will update to SUBMITTED.
*
* Note: a group can only be submitted again if it fails.
*
* @param client - An Algodv2 client
*
* @returns A promise that, upon success, resolves to a list of TxIDs of the submitted transactions.
*/
async submit(client) {
if (this.status > AtomicTransactionComposerStatus.SUBMITTED) {
throw new Error('Transaction group cannot be resubmitted');
}
const stxns = await this.gatherSignatures();
await client.sendRawTransaction(stxns).do();
this.status = AtomicTransactionComposerStatus.SUBMITTED;
return this.txIDs;
}
/**
* Send the transaction group to the network and wait until it's committed to a block. An error
* will be thrown if submission or execution fails.
*
* The composer's status must be SUBMITTED or lower before calling this method, since execution is
* only allowed once. If submission is successful, this composer's status will update to SUBMITTED.
* If the execution is also successful, this composer's status will update to COMMITTED.
*
* Note: a group can only be submitted again if it fails.
*
* @param client - An Algodv2 client
* @param waitRounds - The maximum number of rounds to wait for transaction confirmation
*
* @returns A promise that, upon success, resolves to an object containing the confirmed round for
* this transaction, the txIDs of the submitted transactions, and an array of results containing
* one element for each method call transaction in this group.
*/
async execute(client, waitRounds) {
if (this.status === AtomicTransactionComposerStatus.COMMITTED) {
throw new Error('Transaction group has already been executed successfully');
}
const txIDs = await this.submit(client);
this.status = AtomicTransactionComposerStatus.SUBMITTED;
const firstMethodCallIndex = this.transactions.findIndex((_, index) => this.methodCalls.has(index));
const indexToWaitFor = firstMethodCallIndex === -1 ? 0 : firstMethodCallIndex;
const confirmedTxnInfo = await (0, wait_1.waitForConfirmation)(client, txIDs[indexToWaitFor], waitRounds);
this.status = AtomicTransactionComposerStatus.COMMITTED;
const confirmedRound = confirmedTxnInfo['confirmed-round'];
const methodResults = [];
for (const [txnIndex, method] of this.methodCalls) {
const txID = txIDs[txnIndex];
const methodResult = {
txID,
rawReturnValue: new Uint8Array(),
method,
};
try {
const pendingInfo = txnIndex === firstMethodCallIndex
? confirmedTxnInfo
: // eslint-disable-next-line no-await-in-loop
await client.pendingTransactionInformation(txID).do();
methodResult.txInfo = pendingInfo;
if (method.returns.type !== 'void') {
const logs = pendingInfo.logs || [];
if (logs.length === 0) {
throw new Error('App call transaction did not log a return value');
}
const lastLog = Buffer.from(logs[logs.length - 1], 'base64');
if (lastLog.byteLength < 4 ||
!lastLog.slice(0, 4).equals(RETURN_PREFIX)) {
throw new Error('App call transaction did not log a return value');
}
methodResult.rawReturnValue = new Uint8Array(lastLog.slice(4));
methodResult.returnValue = method.returns.type.decode(methodResult.rawReturnValue);
}
}
catch (err) {
methodResult.decodeError = err;
}
methodResults.push(methodResult);
}
return {
confirmedRound,
txIDs,
methodResults,
};
}
}
exports.AtomicTransactionComposer = AtomicTransactionComposer;
/** The maximum size of an atomic transaction group. */
AtomicTransactionComposer.MAX_GROUP_SIZE = 16;
//# sourceMappingURL=composer.js.mapВыполнить команду
Для локальной разработки. Не используйте в интернете!