@gear-js/api
Version:
A JavaScript library that provides functionality to connect GEAR Component APIs.
298 lines (295 loc) • 12.2 kB
JavaScript
import { blake2AsHex } from '@polkadot/util-crypto';
import { GearTransaction } from './Transaction.js';
import '@polkadot/util';
import { generateVoucherId } from '../utils/generate.js';
import '@polkadot/types';
import '@polkadot/api';
import 'assert';
import '../default/index.js';
import '../metadata/programMetadata.js';
class GearVoucher extends GearTransaction {
/**
* ### Issue a new voucher for a `user` to be used to pay for sending messages to `program_id` program.
* @param spender The voucher holder account id.
* @param value The voucher amount.
* @param duration (optional) The number of the block until which the voucher is valid. If not specified, the voucher is valid in `api.voucher.minDuration` blocks.
* @param programs (optional) The list of programs that the voucher can be used for. If not specified, the voucher can be used for any program.
* @param codeUploading (optional) Whether the voucher can be used for uploading code.
* @returns The voucher id and the extrinsic to submit.
*
* @example
* ```javascript
* import { VoucherIssued } from '@gear-js/api';
* const programId = '0x..';
* const account = '0x...';
* const { extrinsic } = await api.voucher.issue(account, programId, 10000);
* extrinsic.signAndSend(account, ({ events, status }) => {
* if (status.isInBlock) {
* const voucherIssuedEvent = events.find(({event: { method }}) => method === 'VoucherIssued')?.event as VoucherIssued;
*
* if (voucherIssuedEvent) {
* console.log('voucherId:', voucherIssuedEvent.data.voucherId);
* }
* }
* })
* ```
*/
async issue(spender, value, duration, programs, codeUploading = false) {
const nonce = await this._api.query.gearVoucher.issued();
const nextNonce = nonce.unwrapOrDefault().addn(1).toArray('le', 8);
const voucherId = generateVoucherId(nextNonce);
this.extrinsic = this._api.tx.gearVoucher.issue(spender, value, programs || null, codeUploading, duration || this.minDuration);
return { extrinsic: this.extrinsic, voucherId };
}
/**
* Issue a new voucher. This method is available only for runtime versions < 1100.
* @deprecated
* @param to
* @param program
* @param value
* @returns
*/
issueDeprecated(to, program, value) {
const id = generateVoucherId(to, program);
const voucherId = blake2AsHex(id, 256);
this.extrinsic = this._api.tx.gearVoucher.issue.call(this, to, program, value);
return { extrinsic: this.extrinsic, voucherId };
}
/**
* ### Use a voucher to send a message to a program.
* @param voucherId The id of the voucher to be used.
* @param params Either `SendMessage` or `SendReply` call options.
* @returns Extrinsic to submit
* @example
* ```javascript
* const programId = '0x..';
* const voucherId = '0x...';
* const msgTx = api.message.send(...);
* const tx = api.voucher.call(voucherId, { SendMessage: msgTx });
* tx.signAndSend(account, (events) => {
* events.forEach(({ event }) => console.log(event.toHuman()));
* })
* ```
*/
call(voucherId, params) {
if ('SendMessage' in params) {
if (params.SendMessage.method.method !== 'sendMessage') {
throw new Error(`Invalid method name. Expected 'SendMessage' but actual is ${params.SendMessage.method.method}`);
}
const [destination, payload, gasLimit, value, keepAlive] = params.SendMessage.args;
return this._api.tx.gearVoucher.call(voucherId, {
SendMessage: { destination, payload, gasLimit, value, keepAlive },
});
}
if ('SendReply' in params) {
if (params.SendReply.method.method !== 'sendReply') {
throw new Error(`Invalid method name. Expected 'SendReply' but actual is ${params.SendReply.method.method}`);
}
const [replyToId, payload, gasLimit, value, keepAlive] = params.SendReply.args;
return this._api.tx.gearVoucher.call(voucherId, {
SendReply: { replyToId, payload, gasLimit, value, keepAlive },
});
}
if ('UploadCode' in params) {
if (params.UploadCode.method.method !== 'uploadCode') {
throw new Error(`Invalid method name. Expected 'UploadCode' but actual is ${params.UploadCode.method.method}`);
}
const [code] = params.UploadCode.args;
return this._api.tx.gearVoucher.call(voucherId, {
UploadCode: { code },
});
}
if ('DeclineVoucher' in params) {
return this._api.tx.gearVoucher.call(voucherId, { DeclineVoucher: null });
}
throw new Error('Invalid call params');
}
/**
* Use a voucher to send a message to a program. This method is available only for runtime versions < 1100.
* @deprecated
* @param params
* @returns
*/
callDeprecated(params) {
if ('SendMessage' in params) {
if (params.SendMessage.method.method !== 'sendMessage') {
throw new Error(`Invalid method name. Expected 'SendMessage' but actual is ${params.SendMessage.method.method}`);
}
const [destination, payload, gasLimit, value, keepAlive] = params.SendMessage.args;
return this._api.tx.gearVoucher.call.call(this, {
SendMessage: { destination, payload, gasLimit, value, keepAlive },
});
}
else if ('SendReply' in params) {
if (params.SendReply.method.method !== 'sendReply') {
throw new Error(`Invalid method name. Expected 'SendReply' but actual is ${params.SendReply.method.method}`);
}
const [replyToId, payload, gasLimit, value, keepAlive] = params.SendReply.args;
return this._api.tx.gearVoucher.call.call(this, {
SendReply: { replyToId, payload, gasLimit, value, keepAlive },
});
}
throw new Error('Invalid call params');
}
/**
* ### Revoke a voucher.
* @param spender The voucher holder account id.
* @param voucherId The id of the voucher to be revoked.
* @returns Extrinsic to submit
* @example
* ```javascript
* const spenderId = '0x...'
* api.voucher.revoke(spenderId, voucherId).signAndSend(account, (events) => {
* events.forEach(({event}) => console.log(event.toHuman()));
* });
* ```
*/
revoke(spender, voucherId) {
return this._api.tx.gearVoucher.revoke(spender, voucherId);
}
/**
* ### Update a voucher.
* @param spender The voucher holder account id.
* @param voucherId The id of the voucher to be updated.
* @param params The update parameters.
* @returns Extrinsic to submit
*
* @example
* ```javascript
* const spenderId = '0x...'
* api.voucher.update(spenderId, voucherId, { balanceTopUp: 100 * 10 ** 12 }).signAndSend(account, (events) => {
* events.forEach(({event}) => console.log(event.toHuman()));
* });
* ```
*/
update(spender, voucherId, params) {
if (!params.moveOwnership &&
!params.balanceTopUp &&
!params.appendPrograms &&
!params.prolongDuration &&
(params.codeUploading === undefined || params.codeUploading === null)) {
throw new Error('At least one of the parameters must be specified');
}
return this._api.tx.gearVoucher.update(spender, voucherId, params.moveOwnership || null, params.balanceTopUp || null, params.appendPrograms || null, params.codeUploading || null, params.prolongDuration || null);
}
/**
* ### Decline existing and not expired voucher.
* @param voucherId The id of the voucher to be declined
* @returns Extrinsic to submit
*/
decline(voucherId) {
return this._api.tx.gearVoucher.decline(voucherId);
}
/**
* ### Check if a voucher exists.
* @param accountId
* @param programId
* @returns
*/
async exists(accountId, programId) {
const keyPrefixes = this._api.query.gearVoucher.vouchers.keyPrefix(accountId);
const keysPaged = await this._api.rpc.state.getKeysPaged(keyPrefixes, 1000, keyPrefixes);
if (keysPaged.length === 0) {
return false;
}
const vouchers = (await this._api.rpc.state.queryStorageAt(keysPaged));
return !!vouchers.find((item) => {
const typedItem = this._api.createType('Option<PalletGearVoucherInternalVoucherInfo>', item);
if (typedItem.isNone)
return false;
return typedItem.unwrap().programs.unwrapOrDefault().toJSON().includes(programId);
});
}
/**
* ### Get all vouchers for account.
* @param accountId
* @returns
*/
async getAllForAccount(accountId, programId) {
const result = {};
const voucherEntries = await this._api.query.gearVoucher.vouchers.entries(accountId);
if (voucherEntries.length === 0) {
return result;
}
for (const [key, value] of voucherEntries) {
const voucherId = key.args[1].toHex();
const details = value.unwrap();
const programs = details.programs.isSome ? details.programs.unwrap().toJSON() : null;
if (programId && programs !== null && !programs.includes(programId)) {
continue;
}
result[voucherId] = {
owner: details.owner.toHex(),
programs,
expiry: details.expiry.toNumber(),
codeUploading: details.codeUploading.isTrue,
};
}
return result;
}
/**
* ### Get all vouchers issued by an account.
* If many voucher are issued, this may take a while.
* @param accountId - The account id of the owner of the vouchers.
* @returns - An array of voucher ids.
*/
async getAllIssuedByAccount(accountId) {
const result = [];
const keys = await this._api.query.gearVoucher.vouchers.keys();
if (keys.length === 0) {
return result;
}
for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1000) {
const vouchers = (await this._api.rpc.state.queryStorageAt(keys.slice(keyIndex, keyIndex + 1000)));
vouchers.forEach((info, index) => {
if (info.isNone) {
return;
}
if (!info.unwrap().owner.eq(accountId)) {
return;
}
const voucherId = keys[index + keyIndex].args[1].toHex();
result.push(voucherId);
});
}
return result;
}
/**
* ### Get voucher details.
* @param accountId
* @param voucherId
* @returns
*/
async getDetails(accountId, voucherId) {
const voucher = await this._api.query.gearVoucher.vouchers(accountId, voucherId);
if (voucher.isNone) {
return null;
}
const { owner, programs, expiry, codeUploading } = voucher.unwrap();
return {
owner: owner.toHex(),
programs: programs.isSome ? programs.unwrap().toJSON() : null,
expiry: expiry.toNumber(),
codeUploading: codeUploading.isTrue,
};
}
/**
* ### Minimum duration in blocks voucher could be issued/prolonged for.
*/
get minDuration() {
return this._api.consts.gearVoucher.minDuration.toNumber();
}
/**
* ### Maximum duration in blocks voucher could be issued/prolonged for.
*/
get maxDuration() {
return this._api.consts.gearVoucher.maxDuration.toNumber();
}
/**
* ### Maximum amount of programs to be specified to interact with.
*/
get maxProgramsAmount() {
return this._api.consts.gearVoucher.maxProgramsAmount.toNumber();
}
}
export { GearVoucher };