diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 84dc51f391..269a8166b4 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -1,3 +1,13 @@ +## master + +_08/21/2023_ + +### Changes + +https://github.com/gear-tech/gear-js/pull/1353 +- Remove `sendMessageWithVoucher` and `sendReplyWithVoucher` methods according to https://github.com/gear-tech/gear/pull/3083. +- Add optional `prepaid` and `account` fields to `api.message.send` and `api.message.sendReply` method arguments. They are used to send messages with the issued voucher. + ## 0.32.4 _07/25/2023_ diff --git a/api/README.md b/api/README.md index b4bde2584d..7ae6fd6fbf 100644 --- a/api/README.md +++ b/api/README.md @@ -214,9 +214,9 @@ try { value: 1000, }; // In that case payload will be encoded using meta.handle_input type - let extrinsic = gearApi.message.send(message, meta); + let extrinsic = await gearApi.message.send(message, meta); // So if you want to use another type you can specify it - extrinsic = gearApi.message.send(message, meta, meta.async_handle_input); + extrinsic = await gearApi.message.send(message, meta, meta.async_handle_input); } catch (error) { console.error(`${error.name}: ${error.message}`); } @@ -240,7 +240,7 @@ const reply = { gasLimit: 10000000, value: 1000, }; -const extrinsic = gearApi.message.sendReply(reply, meta); +const extrinsic = await gearApi.message.sendReply(reply, meta); await extrinsic(keyring, (events) => { console.log(event.toHuman()); }); @@ -332,6 +332,44 @@ const tx = api.program.resumeSession.commit({ sessionId, blockCount: 20_000 }); tx.signAndSend(account); ``` + +### Issue a voucher +Use `api.voucher.issue` method to issue a new voucher for a user to be used to pay for sending messages to `program_id` program. + +```javascript +import { VoucherIssued } from '@gear-js/api'; + +const programId = '0x..'; +const account = '0x...'; +const tx = api.voucher.issue(account, programId, 10000); +tx.signAndSend(account, (events) => { + const voucherIssuedEvent = events.events.filter(({event: {method}}) => method === 'VoucherIssued') as VoucherIssued; + console.log(voucherIssuedEvent.toJSON()); +}) +``` + +#### Check that the voucher exists for a particular user and program +The `api.voucher.exists` method returns a boolean value indicates whether the voucher exists or not. +```javascript +const voucherExists = await api.voucher.exists(programId, accountId) +``` + +#### Send message and reply with the issued voucher +To send message with voucher all you need to do is to set `prepaid` flag to `true` in the first argument of `api.message.send` and `api.message.sendReply` methods. Also it's good to specify account ID that is used to send the extrinsic to check whether the voucher exists or not. +```javascript +let extrinsic = await api.message.send({ + destination: destination, + payload: somePayload, + gasLimit: 10000000, + value: 1000, + prepaid: true, + account: accountId, +}, meta); +``` + + +#### Send message and reply with issued voucher + ## Work with programs and blockchain state ### Check that the address belongs to some program diff --git a/api/src/Message.ts b/api/src/Message.ts index 7b754f3736..bc546f97f1 100644 --- a/api/src/Message.ts +++ b/api/src/Message.ts @@ -3,12 +3,7 @@ import { HexString } from '@polkadot/util/types'; import { ISubmittableResult } from '@polkadot/types/types'; import { ReplaySubject } from 'rxjs'; -import { - IMessageSendOptions, - IMessageSendReplyOptions, - IMessageSendReplyWithVoucherOptions, - IMessageSendWithVoucherOptions, -} from './types'; +import { IMessageSendOptions, IMessageSendReplyOptions } from './types'; import { SendMessageError, SendReplyError } from './errors'; import { encodePayload, validateGasLimit, validateMailboxItem, validateValue, validateVoucher } from './utils'; import { GearTransaction } from './Transaction'; @@ -18,7 +13,7 @@ import { UserMessageSentData } from './events'; export class GearMessage extends GearTransaction { /** * ## Send Message - * @param args Message parameters + * @param args Message parameters. * @param meta Program metadata obtained using `getProgramMetadata` function. * @param typeIndex (optional) Index of type in the registry. If not specified the type index from `meta.handle.input` will be used instead. * @returns Submittable result @@ -43,7 +38,7 @@ export class GearMessage extends GearTransaction { args: IMessageSendOptions, meta: ProgramMetadata, typeIndex?: number, - ): SubmittableExtrinsic<'promise', ISubmittableResult>; + ): Promise>; /** * ## Send Message @@ -71,7 +66,7 @@ export class GearMessage extends GearTransaction { args: IMessageSendOptions, hexRegistry: HexString, typeIndex: number, - ): SubmittableExtrinsic<'promise', ISubmittableResult>; + ): Promise>; /** * ## Send Message @@ -98,7 +93,7 @@ export class GearMessage extends GearTransaction { args: IMessageSendOptions, metaOrHexRegistry?: ProgramMetadata | HexString, typeName?: string, - ): SubmittableExtrinsic<'promise', ISubmittableResult>; + ): Promise>; /** * ## Send Message @@ -107,18 +102,22 @@ export class GearMessage extends GearTransaction { * @param typeIndexOrTypeName type index in registry or type name * @returns Submitted result */ - send( - { destination, value, gasLimit, ...args }: IMessageSendOptions, + async send( + { destination, value, gasLimit, payload, prepaid, account }: IMessageSendOptions, metaOrHexRegistry?: ProgramMetadata | HexString, typeIndexOrTypeName?: number | string, - ): SubmittableExtrinsic<'promise', ISubmittableResult> { + ): Promise> { validateValue(value, this._api); validateGasLimit(gasLimit, this._api); - const payload = encodePayload(args.payload, metaOrHexRegistry, 'handle', typeIndexOrTypeName); + if (prepaid && account) { + await validateVoucher(destination, account, this._api); + } + + const _payload = encodePayload(payload, metaOrHexRegistry, 'handle', typeIndexOrTypeName); try { - this.extrinsic = this._api.tx.gear.sendMessage(destination, payload, gasLimit, value || 0); + this.extrinsic = this._api.tx.gear.sendMessage(destination, _payload, gasLimit, value || 0, prepaid); return this.extrinsic; } catch (error) { throw new SendMessageError(error.message); @@ -152,7 +151,7 @@ export class GearMessage extends GearTransaction { args: IMessageSendReplyOptions, meta?: ProgramMetadata, typeIndex?: number, - ): SubmittableExtrinsic<'promise', ISubmittableResult>; + ): Promise>; /** * ## Send reply message @@ -180,7 +179,7 @@ export class GearMessage extends GearTransaction { args: IMessageSendReplyOptions, hexRegistry: HexString, typeIndex: number, - ): SubmittableExtrinsic<'promise', ISubmittableResult>; + ): Promise>; /** * ## Send reply message @@ -207,7 +206,7 @@ export class GearMessage extends GearTransaction { args: IMessageSendReplyOptions, metaOrHexRegistry?: ProgramMetadata | HexString, typeName?: string, - ): SubmittableExtrinsic<'promise', ISubmittableResult>; + ): Promise>; /** * Sends reply message @@ -216,240 +215,29 @@ export class GearMessage extends GearTransaction { * @param typeIndexOrTypeName type index in registry or type name * @returns Submitted result */ - sendReply( - { value, gasLimit, replyToId, ...args }: IMessageSendReplyOptions, - metaOrHexRegistry?: ProgramMetadata | HexString, - typeIndexOrTypeName?: number | string, - ): SubmittableExtrinsic<'promise', ISubmittableResult> { - validateValue(value, this._api); - validateGasLimit(gasLimit, this._api); - - const payload = encodePayload(args.payload, metaOrHexRegistry, 'reply', typeIndexOrTypeName); - - try { - this.extrinsic = this._api.tx.gear.sendReply(replyToId, payload, gasLimit, value); - return this.extrinsic; - } catch (error) { - throw new SendReplyError(); - } - } - - /** - * ## Send Message with Voucher - * @param args Message parameters - * @param meta Program metadata obtained using `getProgramMetadata` function. - * @param typeIndex (optional) Index of type in the registry. If not specified the type index from `meta.handle.input` will be used instead. - * @returns Submittable result - * @example - * ```javascript - * const programId = '0x..'; - * const hexMeta = '0x...'; - * const meta = getProgramMetadata(hexMeta); - * const accountId = '0x...' - * - * const tx = await api.message.sendWithVoucher({ - * destination: programId, - * payload: { amazingPayload: { } }, - * gasLimit: 20_000_000, - * account: accountId, - * }, meta, meta.handle.input) - * - * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) - * }) - * ``` - */ - sendWithVoucher( - args: IMessageSendWithVoucherOptions, - meta: ProgramMetadata, - typeIndex?: number, - ): Promise>; - - /** - * ## Send Message with Voucher - * @param args Message parameters - * @param hexRegistry Registry in hex format - * @param typeIndex Index of type in the registry. - * @returns Submitted result - * @example - * ```javascript - * const programId = '0x..'; - * const hexRegistry = '0x...'; - * const accountId = '0x...' - * - * const tx = await api.message.sendWithVoucher({ - * destination: programId, - * payload: { amazingPayload: { ... } }, - * gasLimit: 20_000_000 - * account: accountId, - * }, hexRegistry, 4) - * - * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) - * }) - * ``` - */ - sendWithVoucher( - args: IMessageSendWithVoucherOptions, - hexRegistry: HexString, - typeIndex: number, - ): Promise>; - - /** - * ## Send Message with Voucher - * @param args Message parameters - * @param metaOrHexRegistry (optional) Registry in hex format or ProgramMetadata - * @param typeName payload type (one of the default rust types if metadata or registry don't specified) - * @returns Submitted result - * @example - * ```javascript - * const programId = '0x..'; - * const accountId = '0x...' - * - * const tx = await api.message.sendWithVoucher({ - * destination: programId, - * payload: 'PING', - * gasLimit: 20_000_000, - * account: accountId, - * }, undefined, 'String') - * - * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) - * }) - * ``` - */ - sendWithVoucher( - args: IMessageSendWithVoucherOptions, - metaOrHexRegistry?: ProgramMetadata | HexString, - typeName?: string, - ): Promise>; - async sendWithVoucher( - { destination, value, gasLimit, payload, account }: IMessageSendWithVoucherOptions, + async sendReply( + { value, gasLimit, replyToId, payload, prepaid, account }: IMessageSendReplyOptions, metaOrHexRegistry?: ProgramMetadata | HexString, typeIndexOrTypeName?: number | string, ): Promise> { validateValue(value, this._api); validateGasLimit(gasLimit, this._api); - await validateVoucher(destination, account, this._api); - - const _payload = encodePayload(payload, metaOrHexRegistry, 'handle', typeIndexOrTypeName); - try { - this.extrinsic = this._api.tx.gear.sendMessageWithVoucher(destination, _payload, gasLimit, value || 0); - return this.extrinsic; - } catch (error) { - throw new SendMessageError(error.message); + let source: HexString; + if (account) { + const msg = await validateMailboxItem(account, replyToId, this._api); + source = msg.source.toHex(); } - } - - /** - * ### Send reply message with voucher - * @param args Message parameters - * @param meta Program metadata obtained using `getProgramMetadata` function. - * @param typeIndex (optional) Index of type in the registry. If not specified the type index from `meta.reply.input` will be used instead. - * @returns Submitted result - * @example - * ```javascript - * const replyToMessage = '0x..'; - * const hexMeta = '0x...'; - * const meta = getProgramMetadata(hexMeta); - * const accountId = '0x...' - * - * const tx = await api.message.sendReplyWithVoucher({ - * replyToId: replyToMessage, - * payload: { amazingPayload: { } }, - * gasLimit: 20_000_000, - * account: accountId, - * }, meta, meta.reply.input) - * - * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) - * }) - * ``` - */ - sendReplyWithVoucher( - args: IMessageSendReplyWithVoucherOptions, - meta?: ProgramMetadata, - typeIndex?: number, - ): Promise>; - - /** - * ### Send reply message with voucher - * @param args Message parameters - * @param hexRegistry Registry in hex format - * @param typeIndex Index of type in the registry. - * @returns Submitted result - * @example - * ```javascript - * const replyToMessage = '0x..'; - * const hexRegistry = '0x...'; - * const accountId = '0x...' - * - * const tx = await api.message.sendReplyWithVoucher({ - * replyToId: replyToMessage, - * payload: { amazingPayload: { } }, - * gasLimit: 20_000_000, - * account: accountId, - * }, hexRegistry, 5) - * - * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) - * }) - * ``` - */ - sendReplyWithVoucher( - args: IMessageSendReplyWithVoucherOptions, - hexRegistry: HexString, - typeIndex: number, - ): Promise>; - /** - * ### Send reply message with voucher - * @param args Message parameters - * @param metaOrHexRegistry (optional) Registry in hex format or ProgramMetadata - * @param typeName payload type (one of the default rust types if metadata or registry don't specified) - * @returns Submitted result - * @example - * ```javascript - * const replyToMessage = '0x..'; - * const hexRegistry = '0x...'; - * const accountId = '0x...' - * - * const tx = await api.message.sendReplyWithVoucher({ - * replyToId: replyToMessage, - * payload: { amazingPayload: { } }, - * gasLimit: 20_000_000, - * account: accountId, - * }, hexRegistry, 5) - * - * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) - * }) - * ``` - */ - sendReplyWithVoucher( - args: IMessageSendReplyWithVoucherOptions, - metaOrHexRegistry?: ProgramMetadata | HexString, - typeName?: string, - ): Promise>; - - async sendReplyWithVoucher( - { value, gasLimit, replyToId, payload, account }: IMessageSendReplyWithVoucherOptions, - metaOrHexRegistry?: ProgramMetadata | HexString, - typeIndexOrTypeName?: number | string, - ): Promise> { - validateValue(value, this._api); - validateGasLimit(gasLimit, this._api); - - const { source } = await validateMailboxItem(account, replyToId, this._api); - - await validateVoucher(source.toHex(), account, this._api); + if (prepaid && account && source) { + await validateVoucher(source, account, this._api); + } const _payload = encodePayload(payload, metaOrHexRegistry, 'reply', typeIndexOrTypeName); try { - this.extrinsic = this._api.tx.gear.sendReplyWithVoucher(replyToId, _payload, gasLimit, value); + this.extrinsic = this._api.tx.gear.sendReply(replyToId, _payload, gasLimit, value, prepaid); return this.extrinsic; } catch (error) { throw new SendReplyError(); diff --git a/api/src/Voucher.ts b/api/src/Voucher.ts index 3a43d9aead..5f852b034c 100644 --- a/api/src/Voucher.ts +++ b/api/src/Voucher.ts @@ -16,10 +16,10 @@ export class GearVoucher extends GearTransaction { * @example * ```javascript * const programId = '0x..'; - * const account = '0x...' - * const tx = api.voucher.issue(account, programId, 10000) + * const account = '0x...'; + * const tx = api.voucher.issue(account, programId, 10000); * tx.signAndSend(account, (events) => { - * events.forEach(({event}) => console.log(event.toHuman())) + * events.forEach(({event}) => console.log(event.toHuman())); * }) * ``` */ @@ -30,6 +30,7 @@ export class GearVoucher extends GearTransaction { ): { extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>; voucherId: HexString } { const voucherId = generateVoucherId(to, program); this.extrinsic = this._api.tx.gearVoucher.issue(to, program, value); + this.extrinsic.signAndSend('', () => {}); return { extrinsic: this.extrinsic, voucherId }; } diff --git a/api/src/types/interfaces/message/extrinsic.ts b/api/src/types/interfaces/message/extrinsic.ts index 6268d05e26..d0e9dc16b7 100644 --- a/api/src/types/interfaces/message/extrinsic.ts +++ b/api/src/types/interfaces/message/extrinsic.ts @@ -4,20 +4,35 @@ import { GasLimit, Value } from '../../common'; import { PayloadType } from '../../payload'; export interface IMessageSendOptions { + /** + * The message destination + */ destination: HexString; + /** + * Payload to be sent + */ payload: PayloadType; + /** + * Maximum amount of gas the program can spend before it is halted. + */ gasLimit: GasLimit; + /** + * Balance to be transferred to the program once it's been created. + */ value?: Value; -} - -export interface IMessageSendWithVoucherOptions extends IMessageSendOptions { - account: HexString; + /** + * A flag that indicates whether a voucher should be used. + */ + prepaid?: boolean; + /** + * ID of the account sending the message + */ + account?: HexString; } export interface IMessageSendReplyOptions extends Omit { + /** + * Message ID to which the reply is sending + */ replyToId: HexString; } - -export interface IMessageSendReplyWithVoucherOptions extends IMessageSendReplyOptions { - account: HexString; -} diff --git a/api/src/utils/validate.ts b/api/src/utils/validate.ts index a0e864fac4..12790c7e40 100644 --- a/api/src/utils/validate.ts +++ b/api/src/utils/validate.ts @@ -2,7 +2,7 @@ import { BN, u8aToBigInt } from '@polkadot/util'; import { u128, u64 } from '@polkadot/types'; import { HexString } from '@polkadot/util/types'; -import { GasLimit, Value } from '../types'; +import { GasLimit, UserStoredMessage, Value } from '../types'; import { GearApi } from '../GearApi'; import { ValidationError } from '../errors'; import { generateVoucherId } from './generate'; @@ -59,7 +59,11 @@ export async function validateVoucher(programId: HexString, who: HexString, api: } } -export async function validateMailboxItem(account: HexString, messageId: HexString, api: GearApi) { +export async function validateMailboxItem( + account: HexString, + messageId: HexString, + api: GearApi, +): Promise { const mailbox = await api.mailbox.read(account, messageId); if (!mailbox) { diff --git a/api/test/Gas.test.ts b/api/test/Gas.test.ts index c263458c4e..0e0d5ea58a 100644 --- a/api/test/Gas.test.ts +++ b/api/test/Gas.test.ts @@ -99,7 +99,7 @@ describe('Calculate gas', () => { test('Send message', async () => { expect(gasLimits.handle).toBeDefined(); - const tx = api.message.send( + const tx = await api.message.send( { destination: programId, payload: { input: 'Handle' }, @@ -131,7 +131,7 @@ describe('Calculate gas', () => { test('Send reply', async () => { expect(gasLimits.reply).toBeDefined(); - const tx = api.message.sendReply( + const tx = await api.message.sendReply( { replyToId: messageId, payload: { input: 'Reply' }, diff --git a/api/test/Message.test.ts b/api/test/Message.test.ts index 00f1a307c4..13011433db 100644 --- a/api/test/Message.test.ts +++ b/api/test/Message.test.ts @@ -56,7 +56,7 @@ describe('Gear Message', () => { ]; for (const message of messages) { - const tx = api.message.send( + const tx = await api.message.send( { destination: programId, payload: message.payload, @@ -109,8 +109,8 @@ describe('Gear Message', () => { expect(mailbox.filter((value) => value[0][1] === messageToClaim)).toHaveLength(0); }); - test('Send message with specifying payload type instead of metadata', () => { - const tx = api.message.send({ destination: '0x', gasLimit: 1000, payload: 'PING' }, undefined, 'String'); + test('Send message with specifying payload type instead of metadata', async () => { + const tx = await api.message.send({ destination: '0x', gasLimit: 1000, payload: 'PING' }, undefined, 'String'); expect(tx.args[1].toJSON()).toBe('0x1050494e47'); }); }); diff --git a/api/test/Voucher.test.ts b/api/test/Voucher.test.ts index 09186be5e8..1dada307d0 100644 --- a/api/test/Voucher.test.ts +++ b/api/test/Voucher.test.ts @@ -3,7 +3,7 @@ import { KeyringPair } from '@polkadot/keyring/types'; import { join } from 'path'; import { readFileSync } from 'fs'; -import { GearApi, decodeAddress, generateVoucherId, getProgramMetadata } from '../src'; +import { GearApi, decodeAddress, getProgramMetadata } from '../src'; import { TARGET, TEST_META_META, WS_ADDRESS } from './config'; import { checkInit, getAccount, sendTransaction, sleep } from './utilsFunctions'; @@ -31,15 +31,6 @@ afterAll(async () => { }); describe('Voucher', () => { - // test.only('generate id', () => { - // console.log( - // generateVoucherId( - // '0x90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22', - // '0x9701a152975f0d6caf9d0525f730ff6b0088ba4b4e143c26b417c582568afc5b', - // ), - // ); - // }); - test('Upload test_meta program', async () => { programId = api.program.upload( { @@ -75,7 +66,7 @@ describe('Voucher', () => { }); test('Send msg with voucher', async () => { - const tx = await api.message.sendWithVoucher( + const tx = await api.message.send( { destination: programId, payload: { @@ -87,6 +78,7 @@ describe('Voucher', () => { }, gasLimit: 20_000_000_000, account: charlieRaw, + prepaid: true, }, metadata, ); @@ -105,8 +97,15 @@ describe('Voucher', () => { const msgToReply = mailbox[0][0].id.toHex(); - const tx = await api.message.sendReplyWithVoucher( - { replyToId: msgToReply, account: charlieRaw, gasLimit: 20_000_000_000, value: 0, payload: 'Charlie' }, + const tx = await api.message.sendReply( + { + replyToId: msgToReply, + account: charlieRaw, + gasLimit: 20_000_000_000, + value: 0, + payload: 'Charlie', + prepaid: true, + }, metadata, metadata.types.reply!, ); diff --git a/api/test/Waitlist.test.ts b/api/test/Waitlist.test.ts index a02ad6a989..78093b3d4e 100644 --- a/api/test/Waitlist.test.ts +++ b/api/test/Waitlist.test.ts @@ -33,7 +33,7 @@ afterAll(async () => { describe('GearWaitlist', () => { test("read program's waitlist", async () => { - api.message.send({ destination: programId, payload: '0x00', gasLimit: 20_000_000_000 }); + await api.message.send({ destination: programId, payload: '0x00', gasLimit: 20_000_000_000 }); messageId = (await sendTransaction(api.message, alice, ['MessageQueued']))[0].id.toHex(); const eventData = await messageWaited(messageId); expect(eventData).toBeDefined(); @@ -64,7 +64,7 @@ describe('GearWaitlist', () => { }); test("send one more message and read program's waitlist", async () => { - api.message.send({ destination: programId, payload: '0x00', gasLimit: 20_000_000_000 }); + await api.message.send({ destination: programId, payload: '0x00', gasLimit: 20_000_000_000 }); messageId = (await sendTransaction(api.message, alice, ['MessageQueued']))[0]; const waitlist = await api.waitlist.read(programId); expect(waitlist).toHaveLength(2); diff --git a/api/test/config.ts b/api/test/config.ts index d30ca9701c..dc01a36d79 100644 --- a/api/test/config.ts +++ b/api/test/config.ts @@ -2,6 +2,6 @@ export const TEST_WASM_DIR = 'test/wasm'; export const TARGET = 'programs/target/wasm32-unknown-unknown/release'; export const GEAR_EXAMPLES_WASM_DIR = 'test/wasm/examples'; export const PROGRAMS_DIR = 'programs'; -export const TEST_META_META = `${PROGRAMS_DIR}/test-meta/test_meta.meta.txt`; -export const TEST_GAS_META = `${PROGRAMS_DIR}/test-gas/test_gas.meta.txt`; +export const TEST_META_META = `${TARGET}/test_meta.meta.txt`; +export const TEST_GAS_META = `${TARGET}/test_gas.meta.txt`; export const WS_ADDRESS = 'ws://127.0.0.1:9944';