From 9745c738fef861719e3418e9ada738cbb8d710c7 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Fri, 8 Apr 2022 23:26:28 +0300 Subject: [PATCH] feat(aepp): support external accounts in `onAccount` --- src/account/rpc.js | 59 +++++++++++++++++ .../aepp-wallet-communication/rpc/aepp-rpc.js | 63 ++++++------------- test/integration/index.js | 2 +- test/integration/rpc.js | 19 +++--- 4 files changed, 91 insertions(+), 52 deletions(-) create mode 100644 src/account/rpc.js diff --git a/src/account/rpc.js b/src/account/rpc.js new file mode 100644 index 0000000000..005d231d03 --- /dev/null +++ b/src/account/rpc.js @@ -0,0 +1,59 @@ +/** + * AccountRpc module + * @module @aeternity/aepp-sdk/es/account/rpc + * @export AccountRpc + */ +import AccountBase from './base' +import { METHODS } from '../utils/aepp-wallet-communication/schema' +import { NotImplementedError } from '../utils/errors' + +/** + * Account provided by wallet + * @alias module:@aeternity/aepp-sdk/es/account/rpc + * @function + * @rtype Stamp + * @param {Object} param Init params object + * @param {Object} param.rpcClient RpcClient instance + * @param {Object} param.address RPC account address + * @return {Object} AccountRpc instance + */ +export default AccountBase.compose({ + init ({ rpcClient, address }) { + this._rpcClient = rpcClient + this._address = address + }, + methods: { + sign () { + throw new NotImplementedError('RAW signing using wallet') + }, + async address () { + return this._address + }, + /** + * @function signTransaction + * @instance + * @rtype (tx: String, options = {}) => Promise + * @return {Promise} Signed transaction + */ + async signTransaction (tx, options) { + return this._rpcClient.request(METHODS.sign, { + ...options, + onAccount: this._address, + tx, + returnSigned: true, + networkId: this.getNetworkId(options) + }) + }, + /** + * @function signMessage + * @instance + * @rtype (message: String, options = {}) => Promise + * @return {Promise} Signed message + */ + async signMessage (message, options) { + return this._rpcClient.request( + METHODS.signMessage, { ...options, onAccount: this._address, message } + ) + } + } +}) diff --git a/src/utils/aepp-wallet-communication/rpc/aepp-rpc.js b/src/utils/aepp-wallet-communication/rpc/aepp-rpc.js index 97b6353b24..35d4d39b92 100644 --- a/src/utils/aepp-wallet-communication/rpc/aepp-rpc.js +++ b/src/utils/aepp-wallet-communication/rpc/aepp-rpc.js @@ -8,7 +8,9 @@ * from '@aeternity/aepp-sdk/es/utils/aepp-wallet-communication/rpc/aepp-rpc' */ import { v4 as uuid } from '@aeternity/uuid' -import Ae from '../../../ae' +import AccountResolver from '../../../account/resolver' +import AccountRpc from '../../../account/rpc' +import { decode } from '../../encoder' import RpcClient from './rpc-client' import { getHandler, message } from '../helpers' import { METHODS, RPC_STATUS, VERSION } from '../schema' @@ -100,7 +102,7 @@ const handleMessage = (instance) => async (msg) => { * @param {Object} connection Wallet connection object * @return {Object} */ -export default Ae.compose({ +export default AccountResolver.compose({ async init ({ name, connection, @@ -117,14 +119,25 @@ export default Ae.compose({ this.name = name this.debug = debug + const resolveAccountBase = this._resolveAccount + this._resolveAccount = (account = this.rpcClient?.currentAccount) => { + if (typeof account === 'string') { + decode(account, 'ak') + if (!this.rpcClient?.hasAccessToAccount(account)) { + throw new UnAuthorizedAccountError(account) + } + account = AccountRpc({ rpcClient: this.rpcClient, address: account }) + } + if (!account) this._ensureAccountAccess() + return resolveAccountBase(account) + } + if (connection) { // Init RPCClient await this.connectToWallet(connection) } }, methods: { - sign () { - }, addresses () { this._ensureAccountAccess() return [this.rpcClient.currentAccount, ...Object.keys(this.rpcClient.accounts.connected)] @@ -173,11 +186,6 @@ export default Ae.compose({ this.rpcClient.disconnect() this.rpcClient = null }, - async address ({ onAccount } = {}) { - this._ensureConnected() - this._ensureAccountAccess(onAccount) - return onAccount ?? this.rpcClient.currentAccount - }, /** * Ask address from wallet * @function askAddresses @@ -186,7 +194,6 @@ export default Ae.compose({ * @return {Promise} Address from wallet */ async askAddresses () { - this._ensureConnected() this._ensureAccountAccess() return this.rpcClient.request(METHODS.address) }, @@ -203,35 +210,6 @@ export default Ae.compose({ this._ensureConnected() return this.rpcClient.request(METHODS.subscribeAddress, { type, value }) }, - /** - * Overwriting of `signTransaction` AE method - * All sdk API which use it will be send notification to wallet and wait for callBack - * @function signTransaction - * @instance - * @rtype (tx: String, options = {}) => Promise - * @return {Promise} Signed transaction - */ - async signTransaction (tx, opt = {}) { - this._ensureConnected() - this._ensureAccountAccess(opt.onAccount) - return this.rpcClient.request( - METHODS.sign, - { ...opt, tx, returnSigned: true, networkId: this.getNetworkId() } - ) - }, - /** - * Overwriting of `signMessage` AE method - * All sdk API which use it will be send notification to wallet and wait for callBack - * @function signMessage - * @instance - * @rtype (msg: String, options = {}) => Promise - * @return {Promise} Signed transaction - */ - async signMessage (msg, opt = {}) { - this._ensureConnected() - this._ensureAccountAccess(opt.onAccount) - return this.rpcClient.request(METHODS.signMessage, { ...opt, message: msg }) - }, /** * Send connection request to wallet * @function sendConnectRequest @@ -254,11 +232,8 @@ export default Ae.compose({ if (this.rpcClient?.isConnected()) return throw new NoWalletConnectedError('You are not connected to Wallet') }, - _ensureAccountAccess (onAccount) { - if (onAccount) { - if (this.rpcClient?.hasAccessToAccount(onAccount)) return - throw new UnAuthorizedAccountError(onAccount) - } + _ensureAccountAccess () { + this._ensureConnected() if (this.rpcClient?.currentAccount) return throw new UnsubscribedAccountError() } diff --git a/test/integration/index.js b/test/integration/index.js index 62853512cf..d4a935159c 100644 --- a/test/integration/index.js +++ b/test/integration/index.js @@ -54,7 +54,7 @@ export const BaseAe = async (params = {}, compose = {}) => Universal nodes: [{ name: 'test', instance: await Node({ url, internalUrl, ignoreVersion }) }] }) -const spendPromise = (async () => { +export const spendPromise = (async () => { const ae = await BaseAe({ networkId, withoutGenesisAccount: false }) await ae.awaitHeight(2) await ae.spend(1e26, account.publicKey) diff --git a/test/integration/rpc.js b/test/integration/rpc.js index 188fcdc7d8..eb55765051 100644 --- a/test/integration/rpc.js +++ b/test/integration/rpc.js @@ -24,7 +24,7 @@ import BrowserWindowMessageConnection from '../../src/utils/aepp-wallet-communic import { getBrowserAPI, getHandler } from '../../src/utils/aepp-wallet-communication/helpers' import { METHODS, RPC_STATUS } from '../../src/utils/aepp-wallet-communication/schema' import { generateKeyPair, verify, hash } from '../../src/utils/crypto' -import { compilerUrl, genesisAccount, internalUrl, networkId, publicKey, url, ignoreVersion } from './' +import { compilerUrl, account, internalUrl, networkId, url, ignoreVersion, spendPromise } from './' import { NoBrowserFoundError, NoWalletConnectedError, @@ -62,9 +62,10 @@ describe('Aepp<->Wallet', function () { let wallet before(async () => { + await spendPromise wallet = await RpcWallet({ compilerUrl, - accounts: [genesisAccount], + accounts: [MemoryAccount({ keypair: account })], nodes: [{ name: 'local', instance: node }], name: 'Wallet', onConnection (aepp, { accept, deny }) { @@ -185,7 +186,7 @@ describe('Aepp<->Wallet', function () { subscriptionResponse.subscription.should.be.an('array') subscriptionResponse.subscription.filter(e => e === 'connected').length.should.be.equal(1) subscriptionResponse.address.current.should.be.an('object') - Object.keys(subscriptionResponse.address.current)[0].should.be.equal(publicKey) + Object.keys(subscriptionResponse.address.current)[0].should.be.equal(account.publicKey) subscriptionResponse.address.connected.should.be.an('object') Object.keys(subscriptionResponse.address.connected).length.should.be.equal(1) }) @@ -196,8 +197,12 @@ describe('Aepp<->Wallet', function () { .to.be.rejectedWith(UnAuthorizedAccountError, `You do not have access to account ${publicKey}`) }) + it('aepp accepts key pairs in onAccount', async () => { + await aepp.spend(100, await aepp.address(), { onAccount: account }) + }) + it('Get address: subscribed for accounts', async () => { - (await aepp.address()).should.be.equal(publicKey) + (await aepp.address()).should.be.equal(account.publicKey) }) it('Ask for address: subscribed for accounts -> wallet deny', async () => { @@ -214,7 +219,7 @@ describe('Aepp<->Wallet', function () { } const addressees = await aepp.askAddresses() addressees.length.should.be.equal(2) - addressees[0].should.be.equal(publicKey) + addressees[0].should.be.equal(account.publicKey) }) it('Not authorize', async () => { @@ -533,7 +538,7 @@ describe('Aepp<->Wallet', function () { before(async () => { wallet = await RpcWallet({ compilerUrl, - accounts: [genesisAccount], + accounts: [MemoryAccount({ keypair: account })], nodes: [{ name: 'local', instance: node }], name: 'Wallet', onConnection (aepp, { accept, deny }) { @@ -584,7 +589,7 @@ describe('Aepp<->Wallet', function () { subscriptionResponse.subscription.should.be.an('array') subscriptionResponse.subscription.filter(e => e === 'connected').length.should.be.equal(1) subscriptionResponse.address.current.should.be.an('object') - Object.keys(subscriptionResponse.address.current)[0].should.be.equal(publicKey) + Object.keys(subscriptionResponse.address.current)[0].should.be.equal(account.publicKey) subscriptionResponse.address.connected.should.be.an('object') Object.keys(subscriptionResponse.address.connected).length.should.be.equal(1) })