@archwayhq/arch3-core
Version:
Core library to interact with Archway Network
384 lines • 20.6 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SigningArchwayClient = void 0;
const amino_1 = require("@cosmjs/amino");
const cosmwasm_stargate_1 = require("@cosmjs/cosmwasm-stargate");
const modules_1 = require("@cosmjs/cosmwasm-stargate/build/modules");
const math_1 = require("@cosmjs/math");
const proto_signing_1 = require("@cosmjs/proto-signing");
const stargate_1 = require("@cosmjs/stargate");
const tendermint_rpc_1 = require("@cosmjs/tendermint-rpc");
const utils_1 = require("@cosmjs/utils");
const lodash_1 = __importDefault(require("lodash"));
const modules_2 = require("./modules");
const queryclient_1 = require("./queryclient");
const utils_2 = require("./utils");
const defaultGasAdjustment = 1.5;
function buildResult(response) {
const { height, transactionHash, gasWanted, gasUsed, events, parsedLogs } = response;
return {
logs: parsedLogs,
height,
transactionHash,
events,
gasWanted,
gasUsed,
};
}
const flatFeeRequiredTypes = [
'/cosmwasm.wasm.v1.MsgExecuteContract',
'/cosmwasm.wasm.v1.MsgMigrateContract',
];
/**
* Extension to the {@link SigningCosmWasmClient} for transacting with Archway's modules.
*/
class SigningArchwayClient extends cosmwasm_stargate_1.SigningCosmWasmClient {
constructor(cometClient, signer, options) {
const { registry = new proto_signing_1.Registry([...stargate_1.defaultRegistryTypes, ...modules_1.wasmTypes, ...modules_2.rewardsTypes]), aminoTypes = new stargate_1.AminoTypes({
...(0, stargate_1.createDefaultAminoConverters)(),
...(0, cosmwasm_stargate_1.createWasmAminoConverters)(),
...(0, modules_2.createRewardsAminoConverters)(),
...(0, modules_2.createAuthzAminoConverters)(),
}), gasAdjustment = defaultGasAdjustment, } = options;
super(cometClient, signer, { ...options, registry, aminoTypes });
this.archwaySigner = signer;
this.archwayQueryClient = (0, queryclient_1.createArchwayQueryClient)(cometClient);
this.gasAdjustment = gasAdjustment;
}
/**
* Creates an instance by connecting to the given Tendermint RPC endpoint.
*
* @param endpoint - String URL of the RPC endpoint to connect or an {@link HttpEndpoint} object.
* @param signer - The transaction signer configuration.
* @param options - Options for the signing client.
* @returns A {@link SigningArchwayClient} connected to the endpoint.
*/
static async connectWithSigner(endpoint, signer, options = {}) {
const cometClient = await (0, tendermint_rpc_1.connectComet)(endpoint);
return SigningArchwayClient.createWithSigner(cometClient, signer, options);
}
/**
* Creates an instance by connecting to the given Tendermint RPC endpoint using an HttpBatchClient to batch
* multiple requests and reduce queries to the server.
*
* @param endpoint - String URL of the RPC endpoint to connect or an {@link HttpEndpoint} object.
* @param signer - The transaction signer configuration.
* @param options - Options for the signing client.
* @param batchClientOptions - Optional configuration to control how the HttpBatchClient will batch requests.
* @returns A {@link SigningArchwayClient} connected to the endpoint.
*
* @remarks This factory method doesn't support WebSocket endpoints.
*/
static async connectWithSignerAndBatchClient(endpoint, signer, options, batchClientOptions) {
const cometBatchClient = await (0, utils_2.connectCometWithBatchClient)(endpoint, batchClientOptions);
return SigningArchwayClient.createWithSigner(cometBatchClient, signer, options);
}
/**
* Creates an instance from a manually created CometBFT client.
*
* @param cometClient - A CometBFT client for a given endpoint.
* @param signer - The transaction signer configuration.
* @param options - Options for the signing client.
* @returns A {@link SigningArchwayClient} connected to the endpoint.
*/
/* eslint-disable-next-line @typescript-eslint/require-await */
static async createWithSigner(cometClient, signer, options = {}) {
return new SigningArchwayClient(cometClient, signer, options);
}
/**
* Creates a client in offline mode.
*
* @remarks
* This should only be used in niche cases where you know exactly what you're doing,
* e.g. when building an offline signing application.
*
* When you try to use online functionality with such a signer, an
* exception will be raised.
*
* @param signer - the transaction signer configuration.
* @param options - options for signing and broadcasting transactions.
* @returns An offline {@link SigningArchwayClient}.
*/
/* eslint-disable-next-line @typescript-eslint/require-await */
static async offline(signer, options = {}) {
return new SigningArchwayClient(undefined, signer, options);
}
/**
* Updates the rewards metadata of a contract.
*
* @param senderAddress - Address of the message sender.
* @param metadata - The rewards metadata.
* @param fee - Fee to pay for the transaction. Use 'auto' or a number to calculate the fees automatically.
* When a number is set, it will be used as a gas adjustment multiplier for the estimated fees.
* @param memo - Optional memo to add to the transaction.
* @returns A {@link SetContractMetadataResult} with the contract's metadata.
* @throws Error if the transaction fails.
*
* @see {@link SigningArchwayClient.withdrawContractRewards} for details on how to withdraw rewards.
* @see Check the [Archway Bindings](https://github.com/archway-network/archway-bindings) repository
* for more information on how to withdraw rewards from a contract.
*/
async setContractMetadata(senderAddress, metadata, fee, memo) {
var _a, _b, _c;
const message = modules_2.RewardsMsgEncoder.setContractMetadata({
senderAddress,
metadata: {
contractAddress: metadata.contractAddress,
ownerAddress: (_a = metadata.ownerAddress) !== null && _a !== void 0 ? _a : '',
rewardsAddress: (_b = metadata.rewardsAddress) !== null && _b !== void 0 ? _b : '',
withdrawToWallet: (_c = metadata.withdrawToWallet) !== null && _c !== void 0 ? _c : false,
},
});
const response = await this.assertSignAndBroadcast(senderAddress, [message], fee, memo);
const metadataAttr = stargate_1.logs.findAttribute(response.parsedLogs, 'archway.rewards.v1.ContractMetadataSetEvent', 'metadata');
/* eslint-disable @typescript-eslint/naming-convention */
const contractMetadata = JSON.parse(metadataAttr.value);
/* eslint-enable */
return {
...buildResult(response),
metadata: {
contractAddress: contractMetadata.contract_address,
ownerAddress: contractMetadata.owner_address,
rewardsAddress: contractMetadata.rewards_address,
withdrawToWallet: contractMetadata.withdraw_to_wallet,
},
};
}
/**
* Updates the contract's premium fee. Only the owner of the contract metadata can update the fee.
*
* @param senderAddress - Address of the message sender.
* @param contractAddress - Contract address to set the premium fee.
* @param flatFee - The contract premium fee. To disable the fee, set its `amount` to `0`.
* @param fee - Fee to pay for the transaction. Use 'auto' or a number to calculate the fees automatically.
* When a number is set, it will be used as a gas adjustment multiplier for the estimated fees.
* @param memo - Optional memo to add to the transaction.
* @returns A {@link SetContractPremiumResult} with the contract's premium fee.
* @throws Error if the transaction fails.
*
* @see {@link SigningArchwayClient.withdrawContractRewards} for details on how to withdraw rewards.
* @see Check the [Archway Bindings](https://github.com/archway-network/archway-bindings) repository
* for more information on how to withdraw rewards from a contract.
*/
async setContractPremium(senderAddress, contractAddress, flatFee, fee, memo) {
const message = modules_2.RewardsMsgEncoder.setFlatFee({
senderAddress,
contractAddress,
flatFeeAmount: flatFee,
});
const response = await this.assertSignAndBroadcast(senderAddress, [message], fee, memo);
const flatFeeAttr = stargate_1.logs.findAttribute(response.parsedLogs, 'archway.rewards.v1.ContractFlatFeeSetEvent', 'flat_fee');
return {
...buildResult(response),
premium: {
contractAddress,
flatFee: JSON.parse(flatFeeAttr.value),
},
};
}
/**
* Withdraws rewards for the `senderAddress` up to the given `limit` of records to process.
* If the limit is set to `0`, it will use the default limit from the protocol.
* The default limit is a parameter on the rewards module and it can be updated via governance.
*
* @remarks
* This method is useful when the contract has a large number of rewards to withdraw,
* so they can be processed in batches.
*
* @param senderAddress - Address of the message sender and rewards destination.
* @param limit - Maximum number of rewards to withdraw.
* @param fee - Fee to pay for the transaction. Use 'auto' or a number to calculate the fees automatically.
* When a number is set, it will be used as a gas adjustment multiplier for the estimated fees.
* @param memo - Optional memo to add to the transaction.
* @returns A {@link WithdrawContractRewardsResult} with information about the rewards withdrawn.
* @throws Error if the transaction fails.
*
* @see Check the [Archway Bindings](https://github.com/archway-network/archway-bindings) repository
* for more information on how to withdraw rewards from a contract.
*/
async withdrawContractRewards(senderAddress, limit, fee, memo) {
var _a, _b;
const rewardsAddress = senderAddress;
const message = modules_2.RewardsMsgEncoder.withdrawRewards({
rewardsAddress,
recordsLimit: {
limit: BigInt(limit),
},
});
const response = await this.assertSignAndBroadcast(senderAddress, [message], fee, memo);
const firstLogs = response.parsedLogs.find(() => true);
const rewardsAttr = (_b = (_a = firstLogs === null || firstLogs === void 0 ? void 0 : firstLogs.events.find(event => event.type === 'archway.rewards.v1.RewardsWithdrawEvent')) === null || _a === void 0 ? void 0 : _a.attributes.find(attr => attr.key === 'rewards')) === null || _b === void 0 ? void 0 : _b.value;
const rewards = rewardsAttr ? JSON.parse(rewardsAttr) : [];
return {
...buildResult(response),
rewardsAddress,
rewards,
};
}
/**
* Creates a transaction with the given messages, fee and memo. Then signs and broadcasts the transaction.
*
* When setting the fee to 'auto' or a number, the fee will be calculated automatically based on the messages,
* the minimum price of gas (mPoG) and the minimum consensus fee. If the messages include a contract execution
* or migration, the contract premium fee will be added to the transaction fee.
*
* @param signerAddress - The address that will sign transactions using this instance.
* The signer must be able to sign with this address.
* @param messages - The messages to include in the transaction. The messages types should be registered in the
* {@link SigningArchwayClient.registry} when the client is instantiated.
* @param fee - Fee to pay for the transaction. Use 'auto' or a number to calculate the fees automatically.
* When a number is set, it will be used as a gas adjustment multiplier for the estimated fees.
* @param memo - Optional memo to add to the transaction.
* @returns A {@link DeliverTxResponse} after successfully broadcasting the transaction.
*
* @see {@link SigningArchwayClient.calculateFee} for calculating the fees before broadcasting.
*/
async signAndBroadcast(signerAddress, messages, fee, memo) {
let usedFee;
if (fee === 'auto' || typeof fee === 'number') {
const gasAdjustment = typeof fee === 'number' ? fee : this.gasAdjustment;
usedFee = await this.calculateFee(signerAddress, messages, memo, gasAdjustment);
}
else {
usedFee = fee;
}
return super.signAndBroadcast(signerAddress, messages, usedFee, memo);
}
/**
* Calculates tx fees by simulating the execution of a transaction with the given messages.
* The fee will be calculated based on the minimum price of gas (mPoG) and the minimum consensus
* fee of the network. If the messages include a contract execution or migration, the contract
* premium fee will be added to the calculation.
*
* @param signerAddress - Address used in the gas simulation that will sign transactions.
* The signer must be able to sign with this address.
* @param messages - The messages to include in the transaction for simulating the gas wanted.
* The messages types should be registered in the {@link SigningArchwayClient.registry}
* when the client is instantiated.
* @param memo - Optional memo to add to the transaction.
* @param gasAdjustment - Adjustment factor to be multiplied against the gas estimate.
* @param granter - The granter address that is used for paying with feegrants.
* @param payer - The fee payer address. The payer must have signed the transaction.
* @returns A {@link StdFee} with the estimated fee for the transaction.
*
* @see {@link SigningArchwayClient.simulate} for simulating the execution of a transaction.
* @see {@link SigningArchwayClient.getEstimateTxFees} for getting the minimum price of gas (mPoG) and the minimum
* consensus fee of the network.
*/
async calculateFee(signerAddress, messages, memo, gasAdjustment = this.gasAdjustment, granter, payer) {
const gasEstimation = await this.simulate(signerAddress, messages, memo, granter, payer);
const gas = Math.round(gasEstimation * gasAdjustment);
const { estimatedFee } = await this.getEstimateTxFees(gas);
const fee = await this.includeFlatFees(messages, estimatedFee);
return {
...fee,
granter,
payer,
};
}
async includeFlatFees(messages, fee) {
// We memoize the contract premium fee to avoid querying the same contract multiple times.
const _getContractPremium = lodash_1.default.memoize((contractAddress) => this.getContractPremium(contractAddress));
const flatFees = await Promise.all(messages
.filter(({ typeUrl }) => flatFeeRequiredTypes.includes(typeUrl))
.map(async ({ value }) => {
const contractAddress = lodash_1.default.get(value, 'contract');
const { flatFee } = await _getContractPremium(contractAddress);
return flatFee;
})).then(lodash_1.default.compact); // eslint-disable-line @typescript-eslint/unbound-method
const amount = [...fee.amount, ...flatFees].reduce(amino_1.addCoins);
return {
...fee,
amount: [amount],
};
}
async assertSignAndBroadcast(signerAddress, messages, fee, memo) {
const response = await this.signAndBroadcast(signerAddress, messages, fee, memo);
(0, stargate_1.assertIsDeliverTxSuccess)(response);
const parsedLogs = stargate_1.logs.parseRawLog(response.rawLog);
return {
...response,
parsedLogs,
};
}
/**
* Withdraws staking rewards.
*
* @param delegatorAddress - Address of the delegator withdrawing the staking rewards.
* @param validatorAddress - Address of the validator in the format `archwayval` + hex encoded public key.
* @param fee - Fee to pay for the transaction. Use 'auto' to calculate the fee automatically.
* @param memo - Optional memo to add to the transaction.
* @returns A {@link DeliverTxResponse} with information about the the withdraw tx.
*/
/* istanbul ignore next */
async withdrawRewards(delegatorAddress, validatorAddress, fee, memo) {
return await super.withdrawRewards(delegatorAddress, validatorAddress, fee, memo);
}
/**
* Simulates the transaction and estimates the gas it uses.
* Unlike {@link SigningCosmWasmClient.simulate}, it uses `payer` and `granter`
* arguments during the simulation, to support Archway-specific `cw-fees` mechanics
* and include gas the `SudoMsg::CwGrant` message consumes into the estimation.
*
* @param signerAddress - Address used in the gas simulation that will sign transactions.
* The signer must be able to sign with this address.
* @param messages - The messages to include in the transaction for simulating the gas wanted.
* The messages types should be registered in the {@link SigningArchwayClient.registry}
* when the client is instantiated.
* @param memo - Optional memo to add to the transaction.
* @param granter - The granter address that is used for paying with feegrants.
* @param payer - The fee payer address. The payer must have signed the transaction.
* @returns A number of gas the tx used during the simulation.
*
* @see https://docs.archway.io/developers/guides/cw-fees/introduction
*/
async simulate(signerAddress, messages, memo, granter, payer) {
const anyMsgs = messages.map(m => this.registry.encodeAsAny(m));
const accountFromSigner = (await this.archwaySigner.getAccounts()).find(account => account.address === signerAddress);
if (!accountFromSigner) {
throw new Error('Failed to retrieve account from signer');
}
const pubkey = (0, amino_1.encodeSecp256k1Pubkey)(accountFromSigner.pubkey);
const { sequence } = await this.getSequence(signerAddress);
const { gasInfo } = await this.archwayQueryClient.simulateTx(anyMsgs, memo, pubkey, sequence, granter, payer);
(0, utils_1.assertDefined)(gasInfo);
return math_1.Uint53.fromString(gasInfo.gasUsed.toString()).toNumber();
}
/* istanbul ignore next */
async getBlockRewardsTracking() {
return await this.archwayQueryClient.getBlockRewardsTracking();
}
/* istanbul ignore next */
async getContractMetadata(contractAddress) {
return await this.archwayQueryClient.getContractMetadata(contractAddress);
}
/* istanbul ignore next */
async getContractPremium(contractAddress) {
return await this.archwayQueryClient.getContractPremium(contractAddress);
}
/* istanbul ignore next */
async getEstimateTxFees(gasLimit, contractAddress) {
return await this.archwayQueryClient.getEstimateTxFees(gasLimit, contractAddress);
}
/* istanbul ignore next */
async getOutstandingRewards(rewardsAddress) {
return await this.archwayQueryClient.getOutstandingRewards(rewardsAddress);
}
/* istanbul ignore next */
async getRewardsPool() {
return await this.archwayQueryClient.getRewardsPool();
}
/* istanbul ignore next */
async getAllRewardsRecords(rewardsAddress) {
return await this.archwayQueryClient.getAllRewardsRecords(rewardsAddress);
}
/* istanbul ignore next */
async simulateTx(messages, memo, signer, sequence, granter, payer) {
return await this.archwayQueryClient.simulateTx(messages, memo, signer, sequence, granter, payer);
}
}
exports.SigningArchwayClient = SigningArchwayClient;
//# sourceMappingURL=signingarchwayclient.js.map