@thencc/micronautjs
Version:
MicronautJS is a simplified Algorand transaction library for Serverless Environments
1,274 lines (1,269 loc) • 55.6 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Micronaut: () => Micronaut,
buffer: () => buffer,
default: () => src_default
});
module.exports = __toCommonJS(src_exports);
var import_buffer = require("buffer");
var import_algosdk = __toESM(require("algosdk"));
// src/algo-config.ts
var testnetConfig = {
LEDGER: "testnet",
BASE_SERVER: "https://testnet-api.algonode.cloud",
INDEX_SERVER: "https://testnet-idx.algonode.cloud",
API_TOKEN: "",
PORT: ""
// 443
};
var mainnetConfig = {
LEDGER: "mainnet",
BASE_SERVER: "https://mainnet-api.algonode.cloud",
INDEX_SERVER: "https://mainnet-idx.algonode.cloud",
API_TOKEN: "",
PORT: ""
};
var defaultNodeConfig = testnetConfig;
// src/constants.ts
var defaultLibConfig = {
disableLogs: true
};
// src/utils.ts
var logger = {
enabled: false,
log(...args) {
if (!this.enabled) return;
console.log(...args);
},
debug(...args) {
if (!this.enabled) return;
console.debug(...args);
}
};
// src/index.ts
var Micronaut = class {
/**
* Instantiates Micronaut.js.
*
* @example
* Usage:
*
* ```js
* import { Micronaut } from '@thencc/micronautjs';
* const Micronaut = new Micronaut({
* nodeConfig: {
* BASE_SERVER: 'https://testnet-algorand.api.purestake.io/ps2',
* INDEX_SERVER: 'https://testnet-algorand.api.purestake.io/idx2'
* LEDGER: 'TestNet',
* PORT: '',
* API_TOKEN: { 'X-API-Key': 'YOUR_API_TOKEN' }
* }
* });
* ```
*
* @param config config object
*/
constructor(config) {
// it will be set or it throws an Error
this.indexerClient = void 0;
this.nodeConfig = defaultNodeConfig;
this.libConfig = defaultLibConfig;
// expose entire algosdk in case the dapp needs more
this.sdk = import_algosdk.default;
this.mnemonic = null;
this.address = null;
this.account = null;
this.setNodeConfig(config == null ? void 0 : config.nodeConfig);
this.setLibConfig(config);
}
setLibConfig(config) {
let libConfig;
if (config == void 0) {
libConfig = defaultLibConfig;
} else {
if ("disableLogs" in config && typeof config.disableLogs == "boolean") {
logger.enabled = !config.disableLogs;
}
}
}
/**
* checks if config obj is valid for use
* @param nodeConfig Micronaut config for network + signing mode
* @returns boolean. true is good.
*/
isValidNodeConfig(nodeConfig) {
let isValid = true;
if (nodeConfig == void 0 || !nodeConfig.BASE_SERVER) {
isValid = false;
}
return isValid;
}
/**
* sets config for use (new algod, indexerClient, etc)
* @param nodeConfig Micronaut config for network + signing mode
* - will throw Error if config is lousy
*/
setNodeConfig(nodeConfig) {
logger.log("setNodeConfig", nodeConfig);
if (nodeConfig == void 0) {
nodeConfig = defaultNodeConfig;
}
if (typeof nodeConfig == "string") {
if (nodeConfig == "mainnet") {
nodeConfig = mainnetConfig;
} else if (nodeConfig == "testnet") {
nodeConfig = testnetConfig;
} else {
throw new Error("bad node config string.");
}
}
if (!this.isValidNodeConfig(nodeConfig)) {
throw new Error("bad node config!");
}
if (typeof nodeConfig == "undefined") {
throw new Error("node config undefined");
}
this.nodeConfig = nodeConfig;
this.algodClient = new import_algosdk.Algodv2(nodeConfig.API_TOKEN, nodeConfig.BASE_SERVER, nodeConfig.PORT);
if (nodeConfig.INDEX_SERVER) {
this.indexerClient = new import_algosdk.Indexer(
nodeConfig.API_TOKEN,
nodeConfig.INDEX_SERVER,
nodeConfig.PORT
);
} else {
console.warn("No indexer configured because INDEX_SERVER was not provided.");
}
}
/**
* @returns nodeConfig object or `false` if no nodeConfig is set
*/
getNodeConfig() {
if (this.nodeConfig) return this.nodeConfig;
return false;
}
/**
* Checks status of Algorand network
* @returns Promise resolving to status of Algorand network
*/
async checkStatus() {
if (!this.getNodeConfig()) {
throw new Error("No node configuration set.");
}
const status = await this.algodClient.status().do();
logger.log("Algorand network status: %o", status);
return status;
}
/**
* Connects an account from mnemonic phrase
* @returns void
*/
async connectAccount(mnemonic) {
var _a;
if (!mnemonic) throw new Error("micronaut.mnemonicConnect: No mnemonic provided.");
this.account = (0, import_algosdk.mnemonicToSecretKey)(mnemonic);
this.address = this.account.addr;
if (!(0, import_algosdk.isValidAddress)((_a = this.account) == null ? void 0 : _a.addr)) {
throw new Error("Address is not valid");
}
this.mnemonic = import_algosdk.default.secretKeyToMnemonic(this.account.sk);
}
/**
* General purpose method to await transaction confirmation
* @param txId a string id of the transacion you want to watch
* @param limitDelta how many rounds to wait, defaults to 50
* @param log set to true if you'd like to see "waiting for confirmation" log messages
*/
async waitForConfirmation(txId, limitDelta, log = false) {
var _a;
if (!txId) throw new Error("waitForConfirmation: No transaction ID provided.");
let lastround = (await this.algodClient.status().do())["last-round"];
const limit = lastround + (limitDelta ? limitDelta : 50);
const returnValue = {
status: "fail",
message: ""
};
while (lastround < limit) {
let pendingInfo = "";
try {
pendingInfo = await this.algodClient.pendingTransactionInformation(txId).do();
if (log) {
logger.log("waiting for confirmation");
}
} catch (er) {
console.error((_a = er.response) == null ? void 0 : _a.text);
}
if (pendingInfo["confirmed-round"] !== null && pendingInfo["confirmed-round"] > 0) {
if (log) {
logger.log("Transaction confirmed in round " + pendingInfo["confirmed-round"]);
}
returnValue.txId = txId;
returnValue.status = "success";
returnValue.message = "Transaction confirmed in round " + pendingInfo["confirmed-round"];
break;
}
lastround = (await this.algodClient.status().do())["last-round"];
}
return returnValue;
}
/**
* Creates a LogicSig from a base64 program string. Note that this method does not COMPILE
* the program, just builds an LSig from an already compiled base64 result!
* @param base64ProgramString
* @returns an algosdk LogicSigAccount
*/
generateLogicSig(base64ProgramString) {
if (!base64ProgramString) throw new Error("No program string provided.");
const program = new Uint8Array(import_buffer.Buffer.from(base64ProgramString, "base64"));
return new import_algosdk.LogicSigAccount(program);
}
async atomicOptInAsset(assetIndex, optionalTxnArgs) {
if (!this.address) throw new Error("No account set in Micronaut.");
if (!assetIndex) throw new Error("No asset index provided.");
const suggestedParams = (optionalTxnArgs == null ? void 0 : optionalTxnArgs.suggestedParams) || await this.algodClient.getTransactionParams().do();
const optInTransaction = (0, import_algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject)({
from: this.address,
to: this.address,
assetIndex,
amount: 0,
suggestedParams
});
return {
transaction: optInTransaction,
isLogigSig: false
};
}
/**
* Opt-in the current account for the a token or NFT Asset.
* @param assetIndex number of asset to opt-in to
* @param callbacks `MicronautTxnCallbacks`, passed to {@link sendTransaction}
* @returns Promise resolving to confirmed transaction or error
*/
async optInAsset(assetIndex, callbacks, optionalTxnArgs) {
if (!this.address) throw new Error("There was no account!");
if (!assetIndex) throw new Error("No asset index provided.");
const { transaction } = await this.atomicOptInAsset(assetIndex, optionalTxnArgs);
return await this.sendTransaction(transaction, callbacks);
}
// this is a bit harder with the algosdk api
// what we may want to do be more opinionated and have a standard local
// field we always set on apps when opted in
// OR maybe we check for HAS STATE which might check for local state
// of any kind on that app id?
// async isOptedIntoApp(account: string, appId: number): boolean {
// let optInState = false;
// const accountInfo = await this.getAccountInfo(account);
// accountInfo.assets.forEach((asset: any) => {
// if (asset['asset-id'] == assetId) {
// optInState = true;
// }
// });
// return optInState;
// }
/**
* You can be opted into an asset but still have a zero balance. Use this call
* for cases where you just need to know the address's opt-in state
* @param args object containing `account` and `assetId` properties
* @returns boolean true if account holds asset
*/
async isOptedIntoAsset(args) {
if (!args.account) throw new Error("No account provided.");
if (!args.assetId) throw new Error("No asset ID provided.");
let optInState = false;
const accountInfo = await this.getAccountInfo(args.account);
accountInfo.assets.forEach((asset) => {
if (asset["asset-id"] == args.assetId) {
optInState = true;
}
});
return optInState;
}
/**
* Sync function that returns a correctly-encoded argument array for
* an algo transaction
* @param args must be an any[] array, as it will often need to be
* a mix of strings and numbers. Valid types are: string, number, and bigint
* @returns a Uint8Array of encoded arguments
*/
encodeArguments(args) {
const encodedArgs = [];
args.forEach((arg) => {
if (typeof arg == "number") {
encodedArgs.push((0, import_algosdk.encodeUint64)(arg));
} else if (typeof arg == "bigint") {
encodedArgs.push((0, import_algosdk.encodeUint64)(arg));
} else if (typeof arg == "string") {
encodedArgs.push(new Uint8Array(import_buffer.Buffer.from(arg)));
}
});
return encodedArgs;
}
/**
* Create asset transaction
* @param args : MicronautCreateAssetArguments obj must contain: `assetName`, `symbol`, `decimals`, `amount`.
* @returns atomic txn to create asset
*/
async atomicCreateAsset(args) {
var _a;
if (!args.assetName) throw new Error("args.assetName not provided.");
if (!args.symbol) throw new Error("args.symbol not provided");
if (typeof args.decimals == "undefined") throw new Error("args.decimals not provided.");
if (!args.amount) throw new Error("args.amount not provided.");
const fromAddr = args.from || this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (!args.metaBlock) {
args.metaBlock = " ";
}
if (!args.defaultFrozen) args.defaultFrozen = false;
if (!args.assetURL) args.assetURL = void 0;
const metaBlockLength = args.metaBlock.length;
if (metaBlockLength > 1023) {
console.error("meta block is " + metaBlockLength);
throw new Error("drat! this meta block is too long!");
}
const enc = new TextEncoder();
const note = enc.encode(args.metaBlock);
const totalIssuance = args.amount;
const manager = args.manager && args.manager.length > 0 ? args.manager : fromAddr;
const reserve = args.reserve && args.reserve.length > 0 ? args.reserve : fromAddr;
const freeze = args.freeze && args.freeze.length > 0 ? args.freeze : fromAddr;
const clawback = args.clawback && args.clawback.length > 0 ? args.clawback : fromAddr;
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const txn = (0, import_algosdk.makeAssetCreateTxnWithSuggestedParams)(
fromAddr,
note,
totalIssuance,
args.decimals,
args.defaultFrozen,
manager,
reserve,
freeze,
clawback,
args.symbol,
args.assetName,
args.assetURL,
args.assetMetadataHash,
suggestedParams
);
return {
transaction: txn,
isLogigSig: false
};
}
/**
* Create asset
* @param args MicronautCreateAssetArguments. Must pass `assetName`, `symbol`, `decimals`, `amount`.
* @param callbacks MicronautTxnCallbacks
* @returns asset index
*/
async createAsset(args, callbacks) {
const atomicTxn = await this.atomicCreateAsset(args);
const txn = atomicTxn.transaction;
try {
const txStatus = await this.sendTransaction(txn, callbacks);
const ptx = await this.algodClient.pendingTransactionInformation(txn.txID().toString()).do();
txStatus.createdIndex = ptx["asset-index"];
return txStatus;
} catch (er) {
logger.log("transaction error");
logger.log(er);
throw new Error(er);
}
}
async atomicDeleteAsset(assetId, optionalTxnArgs) {
if (!this.address) throw new Error("there was no account!");
if (!assetId) throw new Error("No assetId provided!");
const enc = new TextEncoder();
const suggestedParams = (optionalTxnArgs == null ? void 0 : optionalTxnArgs.suggestedParams) || await this.algodClient.getTransactionParams().do();
const transaction = (0, import_algosdk.makeAssetDestroyTxnWithSuggestedParams)(
this.address,
enc.encode("doh!"),
// what is this?
assetId,
suggestedParams
);
return {
transaction,
isLogigSig: false
};
}
/**
* Deletes asset
* @param assetId Index of the ASA to delete
* @param callbacks optional MicronautTxnCallbacks
* @returns Promise resolving to confirmed transaction or error
*/
async deleteAsset(assetId, callbacks, optionalTxnArgs) {
if (!assetId) throw new Error("No asset ID provided!");
const { transaction } = await this.atomicDeleteAsset(assetId, optionalTxnArgs);
return await this.sendTransaction(transaction, callbacks);
}
/**
* Creates send asset transaction.
*
* IMPORTANT: Before you can call this, the target account has to "opt-in"
* to the ASA index. You can't just send ASAs to people blind!
*
* @param args - object containing `to`, `assetIndex`, and `amount` properties
* @returns Promise resolving to `MicronautAtomicTransaction`
*/
async atomicSendAsset(args) {
var _a;
if (!args.to) throw new Error("No to address provided");
if (!(0, import_algosdk.isValidAddress)(args.to)) throw new Error("Invalid to address");
if (!args.assetIndex) throw new Error("No asset index provided");
if (!(typeof args.amount == "bigint" || typeof args.amount == "number")) {
throw new Error("Amount has to be a number.");
}
const fromAddr = args.from || this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const transaction = (0, import_algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject)({
from: fromAddr,
to: args.to,
amount: args.amount,
assetIndex: args.assetIndex,
suggestedParams
});
return {
transaction,
isLogigSig: false
};
}
/**
* Sends asset to an address.
*
* IMPORTANT: Before you can call this, the target account has to "opt-in"
* to the ASA index. You can't just send ASAs to people blind!
*
* @param args - object containing `to`, `assetIndex`, and `amount` properties
* @param callbacks optional MicronautTxnCallbacks
* @returns Promise resolving to confirmed transaction or error
*/
async sendAsset(args, callbacks) {
const fromAddr = args.from || this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
const { transaction } = await this.atomicSendAsset(args);
return await this.sendTransaction(transaction, callbacks);
}
/**
* Get info about an asset
* @param assetIndex
* @returns
*/
async getAssetInfo(assetIndex) {
if (!assetIndex) throw new Error("No asset ID provided");
const info = await this.algodClient.getAssetByID(assetIndex).do();
return info;
}
/**
* Creates transaction to opt into an app
* @param args MicronautCallAppArgs
* @returns MicronautAtomicTransaction
*/
async atomicOptInApp(args) {
var _a, _b, _c, _d, _e, _f, _g;
if (!args.appIndex) throw new Error("No app ID provided");
const fromAddr = this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const optInTransaction = (0, import_algosdk.makeApplicationOptInTxnFromObject)({
from: fromAddr,
appIndex: args.appIndex,
suggestedParams,
appArgs: args.appArgs ? this.encodeArguments(args.appArgs) : void 0,
accounts: ((_b = args.optionalFields) == null ? void 0 : _b.accounts) ? (_c = args.optionalFields) == null ? void 0 : _c.accounts : void 0,
foreignApps: ((_d = args.optionalFields) == null ? void 0 : _d.applications) ? (_e = args.optionalFields) == null ? void 0 : _e.applications : void 0,
foreignAssets: ((_f = args.optionalFields) == null ? void 0 : _f.assets) ? (_g = args.optionalFields) == null ? void 0 : _g.assets : void 0
});
return {
transaction: optInTransaction,
isLogigSig: false
};
}
/**
* Opt-in the current account for an app.
* @param args Object containing `appIndex`, `appArgs`, and `optionalFields`
* @param callbacks optional MicronautTxnCallbacks
* @returns Promise resolving to confirmed transaction or error
*/
async optInApp(args, callbacks) {
const { transaction } = await this.atomicOptInApp(args);
return await this.sendTransaction(transaction, callbacks);
}
/**
* Returns atomic transaction that deletes application
* @param appIndex - ID of application
* @returns Promise resolving to atomic transaction that deletes application
*/
async atomicDeleteApp(appIndex, optionalTxnArgs) {
if (!appIndex) throw new Error("No app ID provided");
const fromAddr = this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
const suggestedParams = (optionalTxnArgs == null ? void 0 : optionalTxnArgs.suggestedParams) || await this.algodClient.getTransactionParams().do();
const txn = (0, import_algosdk.makeApplicationDeleteTxn)(fromAddr, suggestedParams, appIndex);
return {
transaction: txn,
isLogigSig: false
};
}
/**
* Deletes an application from the blockchain
* @param appIndex - ID of application
* @param callbacks optional MicronautTxnCallbacks
* @returns Promise resolving to confirmed transaction or error
*/
async deleteApp(appIndex, callbacks, optionalTxnArgs) {
var _a;
try {
const { transaction } = await this.atomicDeleteApp(appIndex, optionalTxnArgs);
const txId = transaction.txID().toString();
const transactionResponse = await this.algodClient.pendingTransactionInformation(txId).do();
const appId = transactionResponse["txn"]["txn"].apid;
return {
status: "success",
message: "deleted app index " + appId,
txId
};
} catch (e) {
logger.log(e);
throw new Error((_a = e.response) == null ? void 0 : _a.text);
}
}
async atomicCallApp(args) {
var _a, _b, _c, _d, _e;
const fromAddr = (args == null ? void 0 : args.from) || this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (!args.appIndex) throw new Error("Must provide appIndex");
if (!args.appArgs.length) throw new Error("Must provide at least one appArgs");
const processedArgs = this.encodeArguments(args.appArgs);
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const callAppTransaction = (0, import_algosdk.makeApplicationNoOpTxnFromObject)({
from: fromAddr,
suggestedParams,
appIndex: args.appIndex,
appArgs: processedArgs,
accounts: ((_b = args.optionalFields) == null ? void 0 : _b.accounts) || void 0,
foreignApps: ((_c = args.optionalFields) == null ? void 0 : _c.applications) || void 0,
foreignAssets: ((_d = args.optionalFields) == null ? void 0 : _d.assets) || void 0,
note: ((_e = args.optionalFields) == null ? void 0 : _e.note) ? this.toUint8Array(args.optionalFields.note) : void 0
});
return {
transaction: callAppTransaction,
isLogigSig: false
};
}
/**
* Call a "method" on a stateful contract. In TEAL, you're really giving
* an argument which branches to a specific place and reads the other args
* @param args Object containing `appIndex`, `appArgs`, and `optionalFields` properties
*/
async callApp(args, callbacks) {
const { transaction } = await this.atomicCallApp(args);
return await this.sendTransaction(transaction, callbacks);
}
async atomicCallAppWithLSig(args) {
var _a, _b, _c, _d;
if (!args.appIndex) throw new Error("Must provide appIndex");
if (!args.appArgs.length) throw new Error("Must provide at least one appArgs");
const processedArgs = this.encodeArguments(args.appArgs);
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const callAppTransaction = (0, import_algosdk.makeApplicationNoOpTxnFromObject)({
from: args.lsig.address(),
suggestedParams,
appIndex: args.appIndex,
appArgs: processedArgs,
accounts: ((_b = args.optionalFields) == null ? void 0 : _b.accounts) || void 0,
foreignApps: ((_c = args.optionalFields) == null ? void 0 : _c.applications) || void 0,
foreignAssets: ((_d = args.optionalFields) == null ? void 0 : _d.assets) || void 0
});
return {
transaction: callAppTransaction,
isLogigSig: true,
lSig: args.lsig
};
}
/**
* Returns an atomic transaction that closes out the user's local state in an application.
* The opposite of {@link atomicOptInApp}.
* @param args Object containing `appIndex`, `appArgs`, and `optionalFields` properties
* @returns Promise resolving to atomic transaction
*/
async atomicCloseOutApp(args) {
var _a, _b, _c, _d;
const fromAddr = (args == null ? void 0 : args.from) || this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (!args.appIndex) throw new Error("Must provide appIndex");
try {
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const processedArgs = this.encodeArguments(args.appArgs);
const closeOutTxn = (0, import_algosdk.makeApplicationCloseOutTxnFromObject)({
from: fromAddr,
suggestedParams,
appIndex: args.appIndex,
appArgs: processedArgs,
accounts: ((_b = args.optionalFields) == null ? void 0 : _b.accounts) || void 0,
foreignApps: ((_c = args.optionalFields) == null ? void 0 : _c.applications) || void 0,
foreignAssets: ((_d = args.optionalFields) == null ? void 0 : _d.assets) || void 0
});
return {
transaction: closeOutTxn,
isLogigSig: false
};
} catch (e) {
throw new Error(e);
}
}
/**
* Closes out the user's local state in an application.
* The opposite of {@link optInApp}.
* @param args Object containing `appIndex`, `appArgs`, and `optionalFields` properties
* @param callbacks optional MicronautTxnCallbacks
* @returns Promise resolving to atomic transaction
*/
async closeOutApp(args, callbacks) {
const { transaction } = await this.atomicCloseOutApp(args);
return await this.sendTransaction(transaction, callbacks);
}
/**
* Get an application's escrow account
* @param appId - ID of application
* @returns Escrow account address as string
*/
getAppEscrowAccount(appId) {
if (!appId) throw new Error("No appId provided");
return (0, import_algosdk.getApplicationAddress)(appId);
}
/**
* Get info about an application (globals, locals, creator address, index)
*
* @param appId - ID of application
* @returns Promise resolving to application state
*/
async getAppInfo(appId) {
if (!appId) throw new Error("No appId provided");
const proms = [this.algodClient.getApplicationByID(appId).do()];
const addr = this.address;
if (addr) {
proms.push(this.getAppLocalState(appId));
}
const promsRes = await Promise.all(proms);
const info = promsRes[0];
const localState = promsRes[1];
const state = {
hasState: true,
globals: [],
locals: (localState == null ? void 0 : localState.locals) || [],
creatorAddress: info.params.creator,
index: appId
};
if (info.params["global-state"]) {
state.globals = this.decodeStateArray(info.params["global-state"]);
}
return state;
}
/**
* Create and deploy a new Smart Contract from TEAL code
*
* @param args MicronautDeployArguments
* @param callbacks optional MicronautTxnCallbacks
* @returns MicronautTransactionStatus
*/
async createApp(args, callbacks) {
var _a, _b, _c, _d, _e;
if (args.optionalFields && args.optionalFields.note && args.optionalFields.note.length > 1023) {
console.warn("drat! your note is too long!");
throw new Error("Your note is too long");
}
const fromAddr = this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (!args.tealApprovalCode) throw new Error("No approval program provided");
if (!args.tealClearCode) throw new Error("No clear program provided");
if (!args.schema) throw new Error("No schema provided");
try {
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
let approvalProgram = new Uint8Array();
let clearProgram = new Uint8Array();
approvalProgram = await this.compileProgram(args.tealApprovalCode);
clearProgram = await this.compileProgram(args.tealClearCode);
if (approvalProgram && clearProgram) {
const txn = (0, import_algosdk.makeApplicationCreateTxnFromObject)({
from: fromAddr,
suggestedParams,
onComplete: import_algosdk.OnApplicationComplete.NoOpOC,
approvalProgram,
clearProgram,
numLocalInts: args.schema.localInts,
numLocalByteSlices: args.schema.localBytes,
numGlobalInts: args.schema.globalInts,
numGlobalByteSlices: args.schema.globalBytes,
appArgs: this.encodeArguments(args.appArgs),
accounts: ((_b = args.optionalFields) == null ? void 0 : _b.accounts) ? args.optionalFields.accounts : void 0,
foreignApps: ((_c = args.optionalFields) == null ? void 0 : _c.applications) ? args.optionalFields.applications : void 0,
foreignAssets: ((_d = args.optionalFields) == null ? void 0 : _d.assets) ? args.optionalFields.assets : void 0,
note: ((_e = args.optionalFields) == null ? void 0 : _e.note) ? this.toUint8Array(args.optionalFields.note) : void 0
});
const txId = txn.txID().toString();
const result = await this.sendTransaction(txn, callbacks);
const transactionResponse = await this.algodClient.pendingTransactionInformation(txId).do();
result.message = "Created App ID: " + transactionResponse["application-index"];
result.createdIndex = transactionResponse["application-index"];
result.meta = transactionResponse;
result.txId = txId;
return result;
} else {
throw new Error("could not compile teal code");
}
} catch (er) {
throw new Error(er.message);
}
}
/**
* Create an atomic transaction to deploy a
* new Smart Contract from TEAL code
*
* @param args MicronautDeployArguments
* @returns MicronautAtomicTransaction
*/
async atomicCreateApp(args) {
var _a, _b, _c, _d, _e;
const fromAddr = this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (!args.tealApprovalCode) throw new Error("No approval program provided");
if (!args.tealClearCode) throw new Error("No clear program provided");
if (!args.schema) throw new Error("No schema provided");
if (args.optionalFields && args.optionalFields.note && args.optionalFields.note.length > 1023) {
throw new Error("Your NOTE is too long, it must be less thatn 1024 Bytes");
} else if (fromAddr) {
try {
const onComplete = import_algosdk.OnApplicationComplete.NoOpOC;
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
let approvalProgram = new Uint8Array();
let clearProgram = new Uint8Array();
approvalProgram = await this.compileProgram(args.tealApprovalCode);
clearProgram = await this.compileProgram(args.tealClearCode);
if (!approvalProgram || !clearProgram) {
throw new Error("Error: you must provide an approval program and a clear state program.");
}
const applicationCreateTransaction = (0, import_algosdk.makeApplicationCreateTxn)(
fromAddr,
suggestedParams,
onComplete,
approvalProgram,
clearProgram,
args.schema.localInts,
args.schema.localBytes,
args.schema.globalInts,
args.schema.globalBytes,
this.encodeArguments(args.appArgs),
((_b = args.optionalFields) == null ? void 0 : _b.accounts) ? args.optionalFields.accounts : void 0,
((_c = args.optionalFields) == null ? void 0 : _c.applications) ? args.optionalFields.applications : void 0,
((_d = args.optionalFields) == null ? void 0 : _d.assets) ? args.optionalFields.assets : void 0,
((_e = args.optionalFields) == null ? void 0 : _e.note) ? this.toUint8Array(args.optionalFields.note) : void 0
);
return {
transaction: applicationCreateTransaction,
isLogigSig: false
};
} catch (er) {
throw new Error("There was an error creating the transaction");
}
} else {
throw new Error("Micronaut.js has no account loaded!");
}
}
/**
* deploys a contract from an lsig account
* keep in mind that the local and global byte and int values have caps,
* 16 for local and 32 for global and that the cost of deploying the
* app goes up based on how many of these slots you want to allocate
*
* @param args MicronautLsigDeployArguments
* @returns
*/
async deployTealWithLSig(args) {
var _a, _b;
if (args.noteText && args.noteText.length > 511) {
throw new Error("Your note is too long");
}
let encodedArgs = [];
if (args.appArgs && args.appArgs.length) {
encodedArgs = this.encodeArguments(args.appArgs);
}
const sender = args.lsig.address();
const onComplete = import_algosdk.OnApplicationComplete.NoOpOC;
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
let approvalProgram = new Uint8Array();
let clearProgram = new Uint8Array();
try {
approvalProgram = await this.compileProgram(args.tealApprovalCode);
clearProgram = await this.compileProgram(args.tealClearCode);
if (approvalProgram && clearProgram) {
const txn = (0, import_algosdk.makeApplicationCreateTxn)(
sender,
suggestedParams,
onComplete,
approvalProgram,
clearProgram,
args.schema.localInts,
args.schema.localBytes,
args.schema.globalInts,
args.schema.globalBytes,
encodedArgs,
((_b = args.optionalFields) == null ? void 0 : _b.accounts) || void 0
);
const txId = txn.txID().toString();
const signedTxn = (0, import_algosdk.signLogicSigTransactionObject)(txn, args.lsig);
await this.algodClient.sendRawTransaction(signedTxn.blob).do();
const transactionResponse = await this.algodClient.pendingTransactionInformation(txId).do();
const appId = transactionResponse["application-index"];
return {
status: "success",
message: "created new app with id: " + appId,
txId
};
} else {
throw new Error("Error compiling programs.");
}
} catch (er) {
console.error("Error deploying contract:");
throw new Error(er);
}
}
/**
* Updates an application with `makeApplicationUpdateTxn`
* @param args MicronautUpdateAppArguments
* @returns atomic transaction that updates the app
*/
async atomicUpdateApp(args) {
var _a, _b, _c, _d, _e;
const fromAddr = this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (!args.tealApprovalCode) throw new Error("No approval program provided");
if (!args.tealClearCode) throw new Error("No clear program provided");
if (args.optionalFields && args.optionalFields.note && args.optionalFields.note.length > 1023) {
throw new Error("Your NOTE is too long, it must be less thatn 1024 Bytes");
}
try {
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
let approvalProgram = new Uint8Array();
let clearProgram = new Uint8Array();
approvalProgram = await this.compileProgram(args.tealApprovalCode);
clearProgram = await this.compileProgram(args.tealClearCode);
if (!approvalProgram || !clearProgram) {
throw new Error("Error: you must provide an approval program and a clear state program.");
}
const applicationCreateTransaction = (0, import_algosdk.makeApplicationUpdateTxn)(
fromAddr,
suggestedParams,
args.appIndex,
approvalProgram,
clearProgram,
this.encodeArguments(args.appArgs),
((_b = args.optionalFields) == null ? void 0 : _b.accounts) ? args.optionalFields.accounts : void 0,
((_c = args.optionalFields) == null ? void 0 : _c.applications) ? args.optionalFields.applications : void 0,
((_d = args.optionalFields) == null ? void 0 : _d.assets) ? args.optionalFields.assets : void 0,
((_e = args.optionalFields) == null ? void 0 : _e.note) ? this.toUint8Array(args.optionalFields.note) : void 0
);
return {
transaction: applicationCreateTransaction,
isLogigSig: false
};
} catch (er) {
throw new Error("There was an error creating the transaction");
}
}
/**
* Sends an update app transaction
* @param args MicronautUpdateAppArguments
* @param callbacks optional callbacks: `onSign`, `onSend`, `onConfirm`
* @returns transaction status
*/
async updateApp(args, callbacks) {
const { transaction } = await this.atomicUpdateApp(args);
return await this.sendTransaction(transaction, callbacks);
}
/**
* Compiles TEAL source via [algodClient.compile](https://py-algorand-sdk.readthedocs.io/en/latest/algosdk/v2client/algod.html#v2client.algod.AlgodClient.compile)
* @param programSource source to compile
* @returns Promise resolving to Buffer of compiled bytes
*/
async compileProgram(programSource) {
const encoder = new TextEncoder();
const programBytes = encoder.encode(programSource);
const compileResponse = await this.algodClient.compile(programBytes).do();
const compiledBytes = new Uint8Array(import_buffer.Buffer.from(compileResponse.result, "base64"));
return compiledBytes;
}
async atomicSendAlgo(args) {
var _a, _b;
if (!(typeof args.amount == "bigint" || typeof args.amount == "number")) {
throw new Error("Amount has to be a number.");
}
if (!args.to) throw new Error("You did not specify a to address");
if (!(0, import_algosdk.isValidAddress)(args.to)) throw new Error("Invalid to address");
const fromAddr = args.from || this.address;
if (!fromAddr) throw new Error("there is no fromAddr");
if (fromAddr) {
const encodedNote = ((_a = args.optionalFields) == null ? void 0 : _a.note) ? this.toUint8Array(args.optionalFields.note) : new Uint8Array();
const suggestedParams = ((_b = args.optionalFields) == null ? void 0 : _b.suggestedParams) || await this.algodClient.getTransactionParams().do();
const transaction = (0, import_algosdk.makePaymentTxnWithSuggestedParamsFromObject)({
from: fromAddr,
to: args.to,
amount: args.amount,
note: encodedNote,
suggestedParams
});
return {
transaction,
isLogigSig: false
};
} else {
throw new Error("there is no fromAddr");
}
}
/**
* Sends ALGO from own account to `args.to`
*
* @param args `MicronautPaymentArgs` object containing `to`, `amount`, and optional `note`
* @param callbacks optional MicronautTxnCallbacks
* @returns Promise resolving to transaction status
*/
async sendAlgo(args, callbacks) {
const { transaction } = await this.atomicSendAlgo(args);
return await this.sendTransaction(transaction, callbacks);
}
/**
* Fetch full account info for an account
* @param address the accress to read info for
* @returns Promise of type AccountInfo
*/
async getAccountInfo(address) {
if (!address) throw new Error("No address provided");
const accountInfo = await this.algodClient.accountInformation(address).do();
return accountInfo;
}
/**
* Checks Algo balance of account
* @param address - Wallet of balance to check
* @returns Promise resolving to Algo balance
*/
async getAlgoBalance(address) {
if (!address) throw new Error("No address provided");
const accountInfo = await this.algodClient.accountInformation(address).do();
return accountInfo.amount;
}
/**
* Checks token balance of account
* @param address - Wallet of balance to check
* @param assetIndex - the ASA index
* @returns Promise resolving to token balance
*/
async getTokenBalance(address, assetIndex) {
if (!address) throw new Error("No address provided");
if (!assetIndex) throw new Error("No asset index provided");
const accountInfo = await this.algodClient.accountInformation(address).do();
let bal = 0;
accountInfo.assets.forEach((asset) => {
if (asset["asset-id"] == assetIndex) {
bal = asset.amount;
}
});
return bal;
}
/**
* Checks if account has at least one token (before playback)
* Keeping this here in case this is a faster/less expensive operation than checking actual balance
* @param address - Address to check
* @param assetIndex - the index of the ASA
*/
async accountHasTokens(address, assetIndex) {
const bal = await this.getTokenBalance(address, assetIndex);
if (bal > 0) {
return true;
} else {
return false;
}
}
/**
* Gets global state for an application.
* @param applicationIndex - the applications index
* @returns {object} object representing global state
*/
async getAppGlobalState(applicationIndex) {
if (!applicationIndex) throw new Error("No application ID provided");
const info = await this.getAppInfo(applicationIndex);
if (info.hasState) {
return this.stateArrayToObject(info.globals);
} else {
return {};
}
}
/**
* Gets account local state for an app. Defaults to AnyWallets.activeAddress unless
* an address is provided.
* @param applicationIndex the applications index
*/
async getAppLocalState(applicationIndex, address) {
if (!applicationIndex) throw new Error("No application ID provided");
const state = {
hasState: false,
globals: [],
locals: [],
creatorAddress: "",
index: applicationIndex
};
if (this.address && !address) {
address = this.address;
}
if (address) {
const accountInfoResponse = await this.algodClient.accountInformation(address).do();
for (let i = 0; i < accountInfoResponse["apps-local-state"].length; i++) {
if (accountInfoResponse["apps-local-state"][i].id == applicationIndex) {
state.hasState = true;
for (let n = 0; n < accountInfoResponse["apps-local-state"][i]["key-value"].length; n++) {
const stateItem = accountInfoResponse["apps-local-state"][i]["key-value"][n];
const key = import_buffer.Buffer.from(stateItem.key, "base64").toString();
const type = stateItem.value.type;
let value = void 0;
let valueAsAddr = "";
if (type == 1) {
value = import_buffer.Buffer.from(stateItem.value.bytes, "base64").toString();
valueAsAddr = (0, import_algosdk.encodeAddress)(import_buffer.Buffer.from(stateItem.value.bytes, "base64"));
} else if (stateItem.value.type == 2) {
value = stateItem.value.uint;
}
state.locals.push({
key,
value: value || "",
address: valueAsAddr
});
}
}
}
return state;
} else {
console.warn("Micronaut used in non-authd state, not getting local vars");
}
}
async atomicAssetTransferWithLSig(args) {
var _a;
if (args.lsig) {
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const transaction = (0, import_algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject)({
from: args.lsig.address(),
to: args.to,
amount: args.amount,
assetIndex: args.assetIndex,
suggestedParams
});
return {
transaction,
isLogigSig: true,
lSig: args.lsig
};
} else {
throw new Error("there is no logic sig object!");
}
}
async atomicPaymentWithLSig(args) {
var _a;
if (args.lsig) {
const suggestedParams = ((_a = args.optionalFields) == null ? void 0 : _a.suggestedParams) || await this.algodClient.getTransactionParams().do();
const transaction = (0, import_algosdk.makePaymentTxnWithSuggestedParamsFromObject)({
from: args.lsig.address(),
to: args.to,
amount: args.amount,
suggestedParams
});
return {
transaction,
isLogigSig: true,
lSig: args.lsig
};
} else {
throw new Error("there is no account!");
}
}
normalizeTxns(txnOrTxns) {
logger.log("normalizeTxns", txnOrTxns);
let txnArr = [];
if (!Array.isArray(txnOrTxns)) {
txnArr = [txnOrTxns];
} else {
txnArr = txnOrTxns;
}
let algoTxnArr = [];
algoTxnArr = txnArr.map((t) => {
let nativeT = t.transaction;
if (nativeT == void 0) {
nativeT = t;
}
return nativeT;
});
logger.log("algoTxnArr", [...algoTxnArr]);
if (algoTxnArr.length > 1) {
algoTxnArr = import_algosdk.default.assignGroupID(algoTxnArr);
logger.log("added group id to txn array");
if (algoTxnArr[0].group) {
const gId = this.txnBuffToB64(algoTxnArr[0].group);
logger.log("gId", gId);
}
}
const txnBuffArr = algoTxnArr.map((t) => t.toByte());
logger.log("txnBuffArr", txnBuffArr);
return txnBuffArr;
}
/**
* Signs a transaction or multiple w the correct wallet according to AW (does not send / submit txn(s) to network)
* @param txnOrTxns Either an array of atomic transactions or a single transaction to sign
* @returns Promise resolving to MicronautTransactionStatus
*/
async signTransaction(txnOrTxns) {
const awTxnsToSign = this.normalizeTxns(txnOrTxns);
logger.log("awTxnsToSign", awTxnsToSign);
let awTxnsSigned;
try {
awTxnsSigned = await this.signTransactions(awTxnsToSign);
logger.log("awTxnsSigned", awTxnsSigned);
} catch (e) {
console.warn("err signing txns...");
logger.log(e);
throw new Error("Error signing transactions");
}
return awTxnsSigned;
}
async signTransactions(transactions, indexesToSign, returnGroup = true) {
const decodedTxns = transactions.map((txn) => {
return (0, import_algosdk.decodeObj)(txn);
});
const signedTxns = [];
const signingResults = [];
for (const idx in decodedTxns) {
const dtxn = decodedTxns[idx];
const isSigned = "txn" in dtxn;
let connectedAddrs = [this.address];
signedTxns.push(transactions[idx]);
if (isSigned) {
continue;
} else if (indexesToSign && indexesToSign.length && !indexesToSign.includes(Number(idx))) {
continue;
} else if (!connectedAddrs.includes((0, import_algosdk.encodeAddress)(dtxn.snd))) {
continue;
}
if (!this.account) {
throw new Error("There is no account loaded to sign with");
}
signedTxns[idx] = new Uint8Array();
const txn = import_algosdk.Transaction.from_obj_for_encoding(dtxn);
const signedTxn = txn.signTxn(this.account.sk);
signingResults.push(signedTxn);
}
let signedIdx = 0;
const formattedTxns = signedTxns.reduce((acc, txn, i) => {
if (txn.length === 0) {
acc.push(signingResults[signedIdx]);
signedIdx += 1;
} else if (returnGroup) {
acc.push(txn);
}
return acc;
}, []);
return Promise.resolve(formattedTxns);
}
/**
* Sends a transaction or multiple w the correct wallet according to AW
* @param txnOrTxns Either an array of atomic transactions or a single transaction to sign
* @param callbacks Optional object with callbacks - `onSign`, `onSend`, and `onConfirm`
* @returns Promise resolving to MicronautTransactionStatus
*/
async sendTransaction(txnOrTxns, callbacks) {
const awTxnsSigned = await this.signTransaction(txnOrTxns);
if (callbacks == null ? void 0 : callbacks.onSign) callbacks.onSign(awTxnsSigned);
const tx = await this.algodClient.sendRawTransaction(awTxnsSigned).do();
if (callbacks == null ? void 0 : callbacks.onSend) callbacks.onSend(tx);
const txStatus = await this.waitForConfirmation(tx.txId);
const transactionResponse = await this.algodClient.pendingTransactionInformation(tx.txId).do();
txStatus.meta = transactionResponse;
if (callbacks == null ? void 0 : callbacks.onConfirm) callbacks.onConfirm(txStatus);
return txStatus;
}
/**
*
* @param str string
* @param enc the encoding type of the string (defaults to utf8)
* @returns string encoded as Uint8Array
*/
toUint8Array(str, enc = "utf8") {
return new Uint8Array(import_buffer.Buffer.from(str, enc));
}
/**
* @deprecated use toUint8Array instead.
* @param str string
* @param enc the encoding type of the string (defaults to utf8)
* @returns string encoded as Uint8Array
*/
to8Arr(str, enc = "utf8") {
return this.toUint8Array(str, enc);
}
/**
* Helper function to turn `globals` and `locals` array into more useful objects
*
* @param stateArray State array returned from functions like {@link getAppInfo}
* @returns A more useful object: `{ array[0].key: array[0].value, array[1].key: array[1].value, ... }`
* TODO add correct typing for this method
*/
stateArrayToObject(stateArray) {
const stateObj = {};
stateArray.forEach((value) => {
if (value.key) stateObj[value.key] = value.value || null;
});
return stateObj;
}
/**
* Used for decoding state
* @param encoded Base64 string
* @returns Human-readable string
*/
b64StrToHumanStr(encoded) {
return import_buffer.Buffer.from(encoded, "base64").toString();
}
/**
* @deprecated Use b64StrToHumanStr instead
* @param encoded Base64 string
* @returns Human-readable string
*/
fromBase64(encoded) {
return this.b64StrToHumanStr(encoded);
}
/**
* Decodes a Base64-encoded Uint8 Algorand address and returns a s