diff --git a/examples/browser/aepp/src/App.vue b/examples/browser/aepp/src/App.vue index e47c10e676..4ae4c33121 100644 --- a/examples/browser/aepp/src/App.vue +++ b/examples/browser/aepp/src/App.vue @@ -18,6 +18,13 @@ > Smart contracts + + Pay for transaction + ({ view: '' }), computed: mapState(['aeSdk']), }; diff --git a/examples/browser/aepp/src/PayForTx.vue b/examples/browser/aepp/src/PayForTx.vue new file mode 100644 index 0000000000..4167ab7b8e --- /dev/null +++ b/examples/browser/aepp/src/PayForTx.vue @@ -0,0 +1,72 @@ + + + diff --git a/examples/browser/aepp/src/components/GenerateSpendTx.vue b/examples/browser/aepp/src/components/GenerateSpendTx.vue new file mode 100644 index 0000000000..c9d9496863 --- /dev/null +++ b/examples/browser/aepp/src/components/GenerateSpendTx.vue @@ -0,0 +1,76 @@ + + + diff --git a/examples/browser/aepp/src/styles.scss b/examples/browser/aepp/src/styles.scss index 77f45e94c3..1ea40925ba 100644 --- a/examples/browser/aepp/src/styles.scss +++ b/examples/browser/aepp/src/styles.scss @@ -30,7 +30,7 @@ h2 { @extend .mt-2, .font-bold, .text-2xl; } -input:not([type=radio]), textarea { +input:not([type=radio]):not([type=checkbox]), textarea { @extend .bg-gray-900, .text-white, .p-2, .w-full; } diff --git a/src/AeSdkWallet.ts b/src/AeSdkWallet.ts index 75ea3f4c16..fb452309d5 100644 --- a/src/AeSdkWallet.ts +++ b/src/AeSdkWallet.ts @@ -243,14 +243,21 @@ export default class AeSdkWallet extends AeSdk { await this.onAskAccounts(id, params, origin); return this.addresses(); }, - [METHODS.sign]: async ({ tx, onAccount = this.address, returnSigned }, origin) => { + [METHODS.sign]: async ( + { + tx, onAccount = this.address, returnSigned, innerTx, + }, + origin, + ) => { if (!this._isRpcClientConnected(id)) throw new RpcNotAuthorizeError(); if (!this.addresses().includes(onAccount)) { throw new RpcPermissionDenyError(onAccount); } - const parameters = { onAccount, aeppOrigin: origin, aeppRpcClientId: id }; - if (returnSigned) { + const parameters = { + onAccount, aeppOrigin: origin, aeppRpcClientId: id, innerTx, + }; + if (returnSigned || innerTx === true) { return { signedTransaction: await this.signTransaction(tx, parameters) }; } try { diff --git a/src/account/Rpc.ts b/src/account/Rpc.ts index c808cb3da8..b4fbbaf6ed 100644 --- a/src/account/Rpc.ts +++ b/src/account/Rpc.ts @@ -35,13 +35,13 @@ export default class AccountRpc extends AccountBase { tx: Encoded.Transaction, { innerTx, networkId }: Parameters[1] = {}, ): Promise { - if (innerTx != null) throw new NotImplementedError('innerTx option in AccountRpc'); if (networkId == null) throw new ArgumentError('networkId', 'provided', networkId); const res = await this._rpcClient.request(METHODS.sign, { onAccount: this.address, tx, returnSigned: true, networkId, + innerTx, }); if (res.signedTransaction == null) { throw new UnsupportedProtocolError('signedTransaction is missed in wallet response'); diff --git a/src/aepp-wallet-communication/rpc/types.ts b/src/aepp-wallet-communication/rpc/types.ts index 1aad2fb343..c7f4ad7015 100644 --- a/src/aepp-wallet-communication/rpc/types.ts +++ b/src/aepp-wallet-communication/rpc/types.ts @@ -55,6 +55,7 @@ export interface WalletApi { * @see {@link https://github.com/aeternity/aepp-sdk-js/commit/153fd89a52c4eab39fcd659b356b36d32129c1ba} */ networkId: string; + innerTx?: boolean; } ) => Promise<{ /** diff --git a/src/tx/validator.ts b/src/tx/validator.ts index 87057152b3..ca17dc8056 100644 --- a/src/tx/validator.ts +++ b/src/tx/validator.ts @@ -144,6 +144,7 @@ validators.push( return [{ message, key: 'InvalidAccountType', checkedKeys: ['tag'] }]; }, // TODO: revert nonce check + // TODO: ensure nonce valid when paying for own tx (tx, { consensusProtocolVersion }) => { const oracleCall = Tag.Oracle === tx.tag || Tag.OracleRegisterTx === tx.tag; const contractCreate = Tag.ContractCreateTx === tx.tag || Tag.GaAttachTx === tx.tag; diff --git a/test/integration/rpc.ts b/test/integration/rpc.ts index 29870d2a4d..cc7ee8af36 100644 --- a/test/integration/rpc.ts +++ b/test/integration/rpc.ts @@ -21,7 +21,6 @@ import { METHODS, RPC_STATUS, generateKeyPair, - hash, verify, NoWalletConnectedError, UnAuthorizedAccountError, @@ -32,7 +31,7 @@ import { verifyMessage, buildTx, } from '../../src'; -import { concatBuffers } from '../../src/utils/other'; +import { getBufferToSign } from '../../src/account/Memory'; import { ImplPostMessage } from '../../src/aepp-wallet-communication/connection/BrowserWindowMessage'; import { getSdk, ignoreVersion, networkId, url, compilerUrl, @@ -300,23 +299,23 @@ describe('Aepp<->Wallet', function aeppWallet() { .to.be.rejectedWith('The peer failed to execute your request due to unknown error'); }); - it('Sign transaction: wallet allow', async () => { - // @ts-expect-error removes object property to restore the original behavior - delete wallet._resolveAccount().signTransaction; - const tx = await aepp.buildTx({ - tag: Tag.SpendTx, - senderId: aepp.address, - recipientId: aepp.address, - amount: 0, - }); + [false, true].forEach((innerTx) => { + it(`Sign${innerTx ? ' inner' : ''} transaction`, async () => { + // @ts-expect-error removes object property to restore the original behavior + delete wallet._resolveAccount().signTransaction; + const tx = await aepp.buildTx({ + tag: Tag.SpendTx, + senderId: aepp.address, + recipientId: aepp.address, + amount: 0, + }); - const signedTx = await aepp.signTransaction(tx); - const unpackedTx = unpackTx(signedTx, Tag.SignedTx); - const { signatures: [signature], encodedTx } = unpackedTx; - const txWithNetwork = concatBuffers([ - Buffer.from(networkId), hash(decode(buildTx(encodedTx))), - ]); - expect(verify(txWithNetwork, signature, aepp.address)).to.be.equal(true); + const signedTx = await aepp.signTransaction(tx, { innerTx }); + const unpackedTx = unpackTx(signedTx, Tag.SignedTx); + const { signatures: [signature], encodedTx } = unpackedTx; + const txWithNetwork = getBufferToSign(buildTx(encodedTx), networkId, innerTx); + expect(verify(txWithNetwork, signature, aepp.address)).to.be.equal(true); + }); }); it('Try to sign using unpermited account', async () => {