diff --git a/examples/abstracted-account.html b/examples/abstracted-account.html new file mode 100644 index 000000000..343d6ec21 --- /dev/null +++ b/examples/abstracted-account.html @@ -0,0 +1,494 @@ + + + + + + + Beacon Example Abstracted account + + + + + + + + + + + + Beacon Example Abstracted Account +

+
+

+ +

+ --- +

+ +

+ + Active account: + + + + +

+ +

+ --- +

+ +

+ --- +

+ + +

+ Test input: + + + + + diff --git a/examples/dapp.html b/examples/dapp.html index 17825fc56..d9c6fa6fd 100644 --- a/examples/dapp.html +++ b/examples/dapp.html @@ -39,12 +39,12 @@

-

-



- +



@@ -201,6 +201,7 @@ ], [beacon.Regions.NORTH_AMERICA_EAST]: [] }, + preferredNetwork: beacon.NetworkType.GHOSTNET, featuredWallets: ['airgap', 'metamask'] // network: { // type: beacon.NetworkType.GHOSTNET @@ -218,6 +219,16 @@ document.getElementById('activeAccount').innerText = activeAccount.address document.getElementById('activeAccountNetwork').innerText = activeAccount.network.type document.getElementById('activeAccountTransport').innerText = activeAccount.origin.type + + if ( + activeAccount.walletType !== 'account_abstracted' && + activeAccount.verificationType !== 'proof_of_event' + ) + return + + const btn = document.getElementById('requestProofOfEventChallenge') + btn.style.opacity = '1' + btn.disabled = false } else { document.getElementById('activeAccount').innerText = '' document.getElementById('activeAccountNetwork').innerText = '' @@ -268,7 +279,7 @@ // send contract call const sendContractCall = () => { return client.getActiveAccount().then(async (activeAccount) => { - const TZ_BUTTON_COLORS_CONTRACT = 'KT1RPW5kTX6WFxg8JK34rGEU24gqEEudyfvz' + const TZ_BUTTON_COLORS_CONTRACT = 'KT1GSdrLzGAorWKoe2z7qV3P5Df6Vmo7neZt' const tokenId = '925' // Setting the color of TzButton is only possible if you are currently the leader and own a color @@ -313,6 +324,27 @@ }) } + // Initiate a Proof Of Event Request + const requestProofOfEventChallenge = () => { + client + .requestProofOfEventChallenge({ + dAppChallengeId: 'my-id', + payload: JSON.stringify({ timestamp: Date.now() }) + }) + .then(() => { + console.log('ProofOfEventChallenge approved') + + const promise = client.getProofOfEventChallenge('my-id') + + promise.then(() => { + alert('Proof of event verified') + }) + }) + .catch((error) => { + console.log('error during Proof Of Event Challenge', error) + }) + } + document.getElementById('connect').addEventListener('click', () => { // Check if we have an active account client.getActiveAccount().then((activeAccount) => { @@ -339,6 +371,11 @@ requestPermission() }) + // Add event listener to the button + document.getElementById('requestProofOfEventChallenge').addEventListener('click', () => { + requestProofOfEventChallenge() + }) + // Add event listener to the button document.getElementById('reset').addEventListener('click', () => { client.destroy().then(() => { diff --git a/examples/index.html b/examples/index.html index 785be45de..6c6ad1a4b 100644 --- a/examples/index.html +++ b/examples/index.html @@ -1,3 +1,4 @@ -DApp
Wallet
Events +DApp
+Wallet
+Events
+Abstracted account
diff --git a/packages/beacon-core/__tests__/managers/AccountManager.spec.ts b/packages/beacon-core/__tests__/managers/AccountManager.spec.ts index 3ff1bf810..3030176e1 100644 --- a/packages/beacon-core/__tests__/managers/AccountManager.spec.ts +++ b/packages/beacon-core/__tests__/managers/AccountManager.spec.ts @@ -17,11 +17,12 @@ const account1: AccountInfo = { type: Origin.P2P, id: 'o1' }, - address: 'tz1', - publicKey: 'pubkey1', + address: 'KT1', network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN], - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: 'abstracted_account', + hasVerifiedChallenge: false } const account2: AccountInfo = { @@ -35,7 +36,8 @@ const account2: AccountInfo = { publicKey: 'pubkey2', network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN], - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: 'implicit' } const account3: AccountInfo = { @@ -49,7 +51,8 @@ const account3: AccountInfo = { publicKey: 'pubkey3', network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN], - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: 'implicit' } describe(`AccountManager`, () => { @@ -87,6 +90,24 @@ describe(`AccountManager`, () => { expect(accountsAfterReplacing[0].scopes, 'after replacing').to.deep.equal(newAccount1.scopes) }) + it(`updates an existing account`, async () => { + const accountsBefore: AccountInfo[] = await manager.getAccounts() + expect(accountsBefore.length, 'before').to.equal(0) + + await manager.addAccount(account1) + const accountsAfterAdding: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfterAdding.length, 'after adding').to.equal(1) + + await manager.updateAccount(account1.accountIdentifier, { + hasVerifiedChallenge: true + }) + const accountsAfterReplacing: AccountInfo[] = await manager.getAccounts() + + expect(accountsAfterReplacing.length, 'after replacing').to.equal(1) + expect(accountsAfterReplacing[0].hasVerifiedChallenge, 'after replacing').to.equal(true) + }) + it(`reads and adds multiple accounts`, async () => { const accountsBefore: AccountInfo[] = await manager.getAccounts() expect(accountsBefore.length, 'before').to.equal(0) diff --git a/packages/beacon-core/src/managers/AccountManager.ts b/packages/beacon-core/src/managers/AccountManager.ts index 723678fad..7f0dba95b 100644 --- a/packages/beacon-core/src/managers/AccountManager.ts +++ b/packages/beacon-core/src/managers/AccountManager.ts @@ -29,6 +29,24 @@ export class AccountManager { ) } + public async updateAccount( + accountIdentifier: string, + accountInfo: Partial + ): Promise { + const account = await this.getAccount(accountIdentifier) + + if (!account) return undefined + + const newAccount = { ...account, ...accountInfo } + await this.storageManager.addOne( + newAccount, + (account) => account.accountIdentifier === accountIdentifier, + true + ) + + return newAccount + } + public async removeAccount(accountIdentifier: string): Promise { return this.storageManager.remove((account) => account.accountIdentifier === accountIdentifier) } diff --git a/packages/beacon-dapp/__tests__/dapp-client.spec.ts b/packages/beacon-dapp/__tests__/dapp-client.spec.ts index b38b3e153..05e15047a 100644 --- a/packages/beacon-dapp/__tests__/dapp-client.spec.ts +++ b/packages/beacon-dapp/__tests__/dapp-client.spec.ts @@ -20,7 +20,8 @@ import { StorageKey, TezosOperationType, TransportStatus, - ExtendedP2PPairingRequest + ExtendedP2PPairingRequest, + ProofOfEventChallengeResponse } from '@airgap/beacon-types' import { MockTransport } from '../../../test/test-utils/MockTransport' @@ -71,11 +72,13 @@ const account1: AccountInfo = { type: Origin.P2P, id: peer1.publicKey }, - address: 'tz1', - publicKey: 'pubkey1', + address: 'KT1', network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN], - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: 'abstracted_account', + verificationType: 'proof_of_event', + hasVerifiedChallenge: false } const account2: AccountInfo = { @@ -89,7 +92,8 @@ const account2: AccountInfo = { publicKey: 'pubkey2', network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN], - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: 'implicit' } /** @@ -161,7 +165,8 @@ describe(`DAppClient`, () => { type: BeaconMessageType.PermissionResponse, publicKey: 'pubkey1', network: { type: NetworkType.MAINNET }, - scopes: [] + scopes: [], + walletType: 'implicit' } const contextInfo: ConnectionContext = { origin: Origin.P2P, @@ -317,7 +322,8 @@ describe(`DAppClient`, () => { publicKey: 'pubkey1', network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN], - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: 'implicit' } const getPeersStub = sinon.stub(DAppClient.prototype, 'getPeer').resolves(peer1) @@ -565,7 +571,8 @@ describe(`DAppClient`, () => { senderId: 'sender-id', publicKey: '444e1f4ab90c304a5ac003d367747aab63815f583ff2330ce159d12c1ecceba1', network: { type: NetworkType.MAINNET }, - scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST] + scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST], + walletType: 'implicit' } const connectionInfo: ConnectionContext = { @@ -622,6 +629,60 @@ describe(`DAppClient`, () => { }) }) + it(`should prepare a proof of event challenge request`, async () => { + const dAppClient = new DAppClient({ name: 'Test', storage: new LocalStorage() }) + + const permissionResponse: ProofOfEventChallengeResponse = { + id: 'my-id', + type: BeaconMessageType.ProofOfEventChallengeResponse, + version: BEACON_VERSION, + senderId: 'sender-id', + dAppChallengeId: 'my-id', + isAccepted: true + } + + const connectionInfo: ConnectionContext = { + origin: Origin.P2P, + id: 'KT1' + } + const makeRequestStub = sinon + .stub(dAppClient, 'makeRequest') + .resolves({ message: permissionResponse, connectionInfo }) + + const notifySuccessStub = sinon.stub(dAppClient, 'notifySuccess').resolves() + + const recordProofOfEventChallengeStub = sinon + .stub(dAppClient, 'recordProofOfEventChallenge') + .resolves() + + const getActiveAccountStub = sinon.stub(dAppClient, 'getActiveAccount').resolves(account1) + + const input = { + dAppChallengeId: 'my-id', + payload: 'my-payload' + } + const response = await dAppClient.requestProofOfEventChallenge(input) + + expect(notifySuccessStub.callCount, 'notifySuccessStub').to.equal(1) + expect(recordProofOfEventChallengeStub.callCount, 'recordProofOfEventChallengeStub').to.equal(1) + expect(getActiveAccountStub.callCount, 'getActiveAccountStub').to.equal(2) + expect(makeRequestStub.callCount).to.equal(1) + expect(makeRequestStub.firstCall.args[0]).to.deep.equal({ + type: BeaconMessageType.ProofOfEventChallengeRequest, + contractAddress: 'KT1', + ...input + }) + delete (response as any).accountInfo + expect(response).to.deep.equal({ + id: 'my-id', + type: 'proof_of_event_challenge_response', + version: BEACON_VERSION, + senderId: 'sender-id', + dAppChallengeId: 'my-id', + isAccepted: true + }) + }) + it(`should prepare a sign payload request (RAW)`, async () => { const dAppClient = new DAppClient({ name: 'Test', storage: new LocalStorage() }) @@ -637,7 +698,8 @@ describe(`DAppClient`, () => { network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST], threshold: undefined, - connectedAt: 1599142450653 + connectedAt: 1599142450653, + walletType: 'implicit' } const getPeerStub = sinon.stub(DAppClient.prototype, 'getPeer').resolves(peer1) @@ -739,7 +801,8 @@ describe(`DAppClient`, () => { network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST], threshold: undefined, - connectedAt: 1599142450653 + connectedAt: 1599142450653, + walletType: 'implicit' } const getPeerStub = sinon.stub(DAppClient.prototype, 'getPeer').resolves(peer1) @@ -807,7 +870,8 @@ describe(`DAppClient`, () => { network: { type: NetworkType.MAINNET }, scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST], threshold: undefined, - connectedAt: 1599142450653 + connectedAt: 1599142450653, + walletType: 'implicit' } const getPeerStub = sinon.stub(DAppClient.prototype, 'getPeer').resolves(peer1) diff --git a/packages/beacon-dapp/src/beacon-message-events.ts b/packages/beacon-dapp/src/beacon-message-events.ts index b6d55acf7..2710b9acc 100644 --- a/packages/beacon-dapp/src/beacon-message-events.ts +++ b/packages/beacon-dapp/src/beacon-message-events.ts @@ -24,6 +24,21 @@ export const messageEvents: { success: BeaconEvent.UNKNOWN, error: BeaconEvent.UNKNOWN }, + [BeaconMessageType.ProofOfEventChallengeRequest]: { + sent: BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SENT, + success: BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS, + error: BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR + }, + [BeaconMessageType.ProofOfEventChallengeResponse]: { + sent: BeaconEvent.UNKNOWN, + success: BeaconEvent.UNKNOWN, + error: BeaconEvent.UNKNOWN + }, + [BeaconMessageType.ProofOfEventChallengeRecorded]: { + sent: BeaconEvent.UNKNOWN, + success: BeaconEvent.UNKNOWN, + error: BeaconEvent.UNKNOWN + }, [BeaconMessageType.OperationRequest]: { sent: BeaconEvent.OPERATION_REQUEST_SENT, success: BeaconEvent.OPERATION_REQUEST_SUCCESS, diff --git a/packages/beacon-dapp/src/dapp-client/DAppClient.ts b/packages/beacon-dapp/src/dapp-client/DAppClient.ts index 3388da746..c87f88a12 100644 --- a/packages/beacon-dapp/src/dapp-client/DAppClient.ts +++ b/packages/beacon-dapp/src/dapp-client/DAppClient.ts @@ -59,6 +59,11 @@ import { ExtensionApp, WebApp, ExtendedWalletConnectPairingResponse, + ProofOfEventChallengeRequest, + ProofOfEventChallengeResponse, + ProofOfEventChallengeRequestInput, + RequestProofOfEventChallengeInput, + ProofOfEventChallengeRecordedMessageInput, ChangeAccountRequest, PeerInfoType // PermissionRequestV3 @@ -84,7 +89,10 @@ import { ExposedPromise, generateGUID, toHex, - prefixPublicKey + signMessage, + CONTRACT_PREFIX, + prefixPublicKey, + isValidAddress } from '@airgap/beacon-utils' import { messageEvents } from '../beacon-message-events' import { BlockExplorer } from '../utils/block-explorer' @@ -110,7 +118,6 @@ import { getWebList, getiOSList } from '@airgap/beacon-ui' -import { signMessage } from '@airgap/beacon-utils' import { WalletConnectTransport } from '@airgap/beacon-transport-walletconnect' const logger = new Logger('DAppClient') @@ -769,7 +776,13 @@ export class DAppClient extends Client { * @param type The type of the message */ public async checkPermissions(type: BeaconMessageType): Promise { - if (type === BeaconMessageType.PermissionRequest) { + if ( + [ + BeaconMessageType.PermissionRequest, + BeaconMessageType.ProofOfEventChallengeRequest, + BeaconMessageType.ProofOfEventChallengeRecorded + ].includes(type) + ) { return true } @@ -1005,8 +1018,18 @@ export class DAppClient extends Client { throw await this.handleRequestError(request, requestError) }) + console.log('######## MESSAGE #######') + console.log(message) + const accountInfo = await this.onNewAccount(message, connectionInfo) + console.log('######## ACCOUNT INFO #######') + + console.log(JSON.stringify(accountInfo)) + + await this.accountManager.addAccount(accountInfo) + await this.setActiveAccount(accountInfo) + const output: PermissionResponseOutput = { ...message, walletKey: accountInfo.walletKey, @@ -1029,6 +1052,85 @@ export class DAppClient extends Client { return output } + /** + * Send a proof of event request to the wallet. The wallet will either accept or decline the challenge. + * If it is accepted, the challenge will be stored, meaning that even if the user refresh the page, the DAppClient will keep checking if the challenge has been fulfilled. + * Once the challenge is stored, a challenge stored message will be sent to the wallet. + * It's **highly recommended** to run a proof of event challenge to check the identity of an abstracted account + * + * @param input The message details we need to prepare the ProofOfEventChallenge message. + */ + public async requestProofOfEventChallenge(input: RequestProofOfEventChallengeInput) { + const activeAccount = await this.getActiveAccount() + + if (!activeAccount) + throw new Error('Please request permissions before doing a proof of event challenge') + if ( + activeAccount.walletType !== 'abstracted_account' && + activeAccount.verificationType !== 'proof_of_event' + ) + throw new Error( + 'This wallet is not an abstracted account and thus cannot perform proof of event' + ) + + const request: ProofOfEventChallengeRequestInput = { + type: BeaconMessageType.ProofOfEventChallengeRequest, + contractAddress: activeAccount.address, + ...input + } + + const { message, connectionInfo } = await this.makeRequest< + ProofOfEventChallengeRequest, + ProofOfEventChallengeResponse + >(request).catch(async (requestError: ErrorResponse) => { + throw await this.handleRequestError(request, requestError) + }) + + this.analytics.track( + 'event', + 'DAppClient', + `Proof of event challenge ${message.isAccepted ? 'accepted' : 'refused'}`, + { address: activeAccount.address } + ) + + if (message.isAccepted) { + await this.recordProofOfEventChallenge(input) + } + + await this.notifySuccess(request, { + account: activeAccount, + output: message, + blockExplorer: this.blockExplorer, + connectionContext: connectionInfo, + walletInfo: await this.getWalletInfo() + }) + + return message + } + + private async recordProofOfEventChallenge(input: RequestProofOfEventChallengeInput) { + const activeAccount = await this.getActiveAccount() + + if (!activeAccount) + throw new Error( + 'Active account is undefined. Please request permissions before recording a proof of event challenge' + ) + + let success = true + let errorMessage = '' + + const recordedRequest: ProofOfEventChallengeRecordedMessageInput = { + type: BeaconMessageType.ProofOfEventChallengeRecorded, + dAppChallengeId: input.dAppChallengeId, + success, + errorMessage + } + + await this.makeRequest(recordedRequest, true).catch(async (requestError: ErrorResponse) => { + throw await this.handleRequestError(recordedRequest, requestError) + }) + } + /** * This method will send a "SignPayloadRequest" to the wallet. This method is meant to be used to sign * arbitrary data (eg. a string). It will return the signature in the format of "edsig..." @@ -1416,6 +1518,13 @@ export class DAppClient extends Client { connectionContext: ConnectionContext walletInfo: WalletInfo } + | { + account: AccountInfo + output: ProofOfEventChallengeResponse + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } | { account: AccountInfo output: OperationResponseOutput @@ -1541,13 +1650,24 @@ export class DAppClient extends Client { * * @param requestInput The BeaconMessage to be sent to the wallet * @param account The account that the message will be sent to + * @param skipResponse If true, the function return as soon as the message is sent */ - private async makeRequest( - requestInput: Optional + + private makeRequest( + requestInput: Optional, + skipResponse?: undefined | false ): Promise<{ message: U connectionInfo: ConnectionContext - }> { + }> + private makeRequest( + requestInput: Optional, + skipResponse: true + ): Promise + private async makeRequest( + requestInput: Optional, + skipResponse?: boolean + ) { const messageId = await generateGUID() logger.time(true, messageId) @@ -1593,15 +1713,19 @@ export class DAppClient extends Client { ...requestInput } - const exposed = new ExposedPromise< - { - message: BeaconMessage | BeaconMessageWrapper - connectionInfo: ConnectionContext - }, - ErrorResponse - >() + let exposed - this.addOpenRequest(request.id, exposed) + if (!skipResponse) { + exposed = new ExposedPromise< + { + message: BeaconMessage | BeaconMessageWrapper + connectionInfo: ConnectionContext + }, + ErrorResponse + >() + + this.addOpenRequest(request.id, exposed) + } const payload = await new Serializer().serialize(request) @@ -1648,7 +1772,7 @@ export class DAppClient extends Client { .catch((emitError) => console.warn(emitError)) // eslint-disable-next-line @typescript-eslint/no-explicit-any - return exposed.promise as any // TODO: fix type + return exposed?.promise as any // TODO: fix type } /** @@ -1857,10 +1981,29 @@ export class DAppClient extends Client { connectionInfo: ConnectionContext ): Promise { // TODO: Migration code. Remove sometime after 1.0.0 release. - const publicKey = await prefixPublicKey( + const tempPK: string | undefined = message.publicKey || (message as any).pubkey || (message as any).pubKey - ) - const address = await getAddressFromPublicKey(publicKey) + + const publicKey = !!tempPK ? await prefixPublicKey(tempPK) : undefined + + if (!publicKey && !message.address) { + throw new Error('PublicKey or Address must be defined') + } + + const address = message.address ?? (await getAddressFromPublicKey(publicKey!)) + + if (isValidAddress(address)) { + throw new Error(`Invalid address: "${address}"`) + } + + if ( + message.walletType === 'abstracted_account' && + address.substring(0, 3) !== CONTRACT_PREFIX + ) { + throw new Error( + `Invalid abstracted account address "${address}", it should be a ${CONTRACT_PREFIX} address` + ) + } logger.log('######## MESSAGE #######') logger.log('onNewAccount', message) @@ -1881,7 +2024,10 @@ export class DAppClient extends Client { scopes: message.scopes, threshold: message.threshold, notification: message.notification, - connectedAt: new Date().getTime() + connectedAt: new Date().getTime(), + walletType: message.walletType ?? 'implicit', + verificationType: message.verificationType, + ...(message.verificationType === 'proof_of_event' ? { hasVerifiedChallenge: false } : {}) } logger.log('accountInfo', '######## ACCOUNT INFO #######') diff --git a/packages/beacon-dapp/src/events.ts b/packages/beacon-dapp/src/events.ts index a37781bd7..41280a929 100644 --- a/packages/beacon-dapp/src/events.ts +++ b/packages/beacon-dapp/src/events.ts @@ -39,6 +39,7 @@ import { } from '@airgap/beacon-core' import { shortenString } from './utils/shorten-string' import { isMobile } from '@airgap/beacon-ui' +import { ProofOfEventChallengeResponseOutput } from '@airgap/beacon-types' const logger = new Logger('BeaconEvents') @@ -61,6 +62,9 @@ export enum BeaconEvent { PERMISSION_REQUEST_SENT = 'PERMISSION_REQUEST_SENT', PERMISSION_REQUEST_SUCCESS = 'PERMISSION_REQUEST_SUCCESS', PERMISSION_REQUEST_ERROR = 'PERMISSION_REQUEST_ERROR', + PROOF_OF_EVENT_CHALLENGE_REQUEST_SENT = 'PROOF_OF_EVENT_CHALLENGE_REQUEST_SENT', + PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS = 'PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS', + PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR = 'PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR', OPERATION_REQUEST_SENT = 'OPERATION_REQUEST_SENT', OPERATION_REQUEST_SUCCESS = 'OPERATION_REQUEST_SUCCESS', OPERATION_REQUEST_ERROR = 'OPERATION_REQUEST_ERROR', @@ -118,6 +122,18 @@ export interface BeaconEventType { walletInfo: WalletInfo } [BeaconEvent.PERMISSION_REQUEST_ERROR]: { errorResponse: ErrorResponse; walletInfo: WalletInfo } + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SENT]: RequestSentInfo + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS]: { + account: AccountInfo + output: ProofOfEventChallengeResponseOutput + blockExplorer: BlockExplorer + connectionContext: ConnectionContext + walletInfo: WalletInfo + } + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR]: { + errorResponse: ErrorResponse + walletInfo: WalletInfo + } [BeaconEvent.OPERATION_REQUEST_SENT]: RequestSentInfo [BeaconEvent.OPERATION_REQUEST_SUCCESS]: { account: AccountInfo @@ -482,6 +498,25 @@ const showPermissionSuccessAlert = async ( }) } +const showProofOfEventChallengeSuccessAlert = async ( + data: BeaconEventType[BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS] +): Promise => { + const { output } = data + + await openToast({ + body: `{{wallet}}\u00A0 has ${output.isAccepted ? 'accepted' : 'refused'} the challenge`, + timer: SUCCESS_TIMER, + walletInfo: data.walletInfo, + state: 'finished', + actions: [ + { + text: 'Challenge Id', + actionText: output.dAppChallengeId + } + ] + }) +} + /** * Show an "Operation Broadcasted" alert * @@ -639,6 +674,9 @@ export const defaultEventCallbacks: { [BeaconEvent.PERMISSION_REQUEST_SENT]: showSentToast, [BeaconEvent.PERMISSION_REQUEST_SUCCESS]: showPermissionSuccessAlert, [BeaconEvent.PERMISSION_REQUEST_ERROR]: showErrorToast, + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SENT]: showSentToast, + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS]: showProofOfEventChallengeSuccessAlert, + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR]: showErrorToast, [BeaconEvent.OPERATION_REQUEST_SENT]: showSentToast, [BeaconEvent.OPERATION_REQUEST_SUCCESS]: showOperationSuccessAlert, [BeaconEvent.OPERATION_REQUEST_ERROR]: showErrorToast, @@ -679,6 +717,15 @@ export class BeaconEventHandler { [BeaconEvent.PERMISSION_REQUEST_SENT]: [defaultEventCallbacks.PERMISSION_REQUEST_SENT], [BeaconEvent.PERMISSION_REQUEST_SUCCESS]: [defaultEventCallbacks.PERMISSION_REQUEST_SUCCESS], [BeaconEvent.PERMISSION_REQUEST_ERROR]: [defaultEventCallbacks.PERMISSION_REQUEST_ERROR], + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SENT]: [ + defaultEventCallbacks.PERMISSION_REQUEST_SENT + ], + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS]: [ + defaultEventCallbacks.PROOF_OF_EVENT_CHALLENGE_REQUEST_SUCCESS + ], + [BeaconEvent.PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR]: [ + defaultEventCallbacks.PROOF_OF_EVENT_CHALLENGE_REQUEST_ERROR + ], [BeaconEvent.OPERATION_REQUEST_SENT]: [defaultEventCallbacks.OPERATION_REQUEST_SENT], [BeaconEvent.OPERATION_REQUEST_SUCCESS]: [defaultEventCallbacks.OPERATION_REQUEST_SUCCESS], [BeaconEvent.OPERATION_REQUEST_ERROR]: [defaultEventCallbacks.OPERATION_REQUEST_ERROR], diff --git a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts index b1e77db9b..a1e49e680 100644 --- a/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts +++ b/packages/beacon-transport-walletconnect/src/communication-client/WalletConnectCommunicationClient.ts @@ -252,7 +252,8 @@ export class WalletConnectCommunicationClient extends CommunicationClient { publicKey, network: message.network, scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST], - id: this.currentMessageId! + id: this.currentMessageId!, + walletType: 'implicit' } this.notifyListeners(session.pairingTopic, permissionResponse) @@ -475,7 +476,8 @@ export class WalletConnectCommunicationClient extends CommunicationClient { type: BeaconMessageType.ChangeAccountRequest, publicKey, network: { type: chainId as NetworkType }, - scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST] + scopes: [PermissionScope.SIGN, PermissionScope.OPERATION_REQUEST], + walletType: 'implicit' }) } } catch {} diff --git a/packages/beacon-types/src/index.ts b/packages/beacon-types/src/index.ts index 52d946b48..f2bc74dda 100644 --- a/packages/beacon-types/src/index.ts +++ b/packages/beacon-types/src/index.ts @@ -4,6 +4,9 @@ */ import { AppMetadata } from './types/beacon/AppMetadata' import { PermissionRequest } from './types/beacon/messages/PermissionRequest' +import { ProofOfEventChallengeRequest } from './types/beacon/messages/ProofOfEventChallengeRequest' +import { ProofOfEventChallengeResponse } from './types/beacon/messages/ProofOfEventChallengeResponse' +import { ProofOfEventChallengeRecordedRequest } from './types/beacon/messages/ProofOfEventChallengeRecordedRequest' import { Network } from './types/beacon/Network' import { BeaconBaseMessage } from './types/beacon/BeaconBaseMessage' import { BeaconMessageType } from './types/beacon/BeaconMessageType' @@ -49,6 +52,7 @@ import { StorageKeyReturnType } from './types/storage/StorageKeyReturnType' import { ExtendedP2PPairingRequest, P2PPairingRequest } from './types/P2PPairingRequest' import { BeaconMessage } from './types/beacon/BeaconMessage' import { RequestPermissionInput } from './types/RequestPermissionInput' +import { RequestProofOfEventChallengeInput } from './types/RequestProofOfEventChallengeInput' import { RequestSignPayloadInput } from './types/RequestSignPayloadInput' // import { RequestEncryptPayloadInput } from './types/RequestEncryptPayloadInput' import { RequestOperationInput } from './types/RequestOperationInput' @@ -70,7 +74,8 @@ import { // EncryptPayloadResponseOutput, OperationResponseOutput, BroadcastResponseOutput, - BeaconResponseOutputMessage + BeaconResponseOutputMessage, + ProofOfEventChallengeResponseOutput } from './types/beacon/messages/BeaconResponseOutputMessage' import { PermissionRequestInput, @@ -79,7 +84,9 @@ import { OperationRequestInput, BroadcastRequestInput, BeaconRequestInputMessage, - IgnoredRequestInputProperties + IgnoredRequestInputProperties, + ProofOfEventChallengeRecordedMessageInput, + ProofOfEventChallengeRequestInput } from './types/beacon/messages/BeaconRequestInputMessage' import { PermissionRequestOutput, @@ -87,7 +94,9 @@ import { // EncryptPayloadRequestOutput, OperationRequestOutput, BroadcastRequestOutput, - BeaconRequestOutputMessage + BeaconRequestOutputMessage, + ProofOfEventChallengeRequestOutput, + ProofOfEventChallengeRecordedMessageOutput } from './types/beacon/messages/BeaconRequestOutputMessage' import { PermissionInfo } from './types/PermissionInfo' import { ConnectionContext } from './types/ConnectionContext' @@ -202,12 +211,17 @@ export { Extension, EncryptedExtensionMessage, RequestPermissionInput, + RequestProofOfEventChallengeInput, RequestSignPayloadInput, + ProofOfEventChallengeRecordedMessageInput, // RequestEncryptPayloadInput, RequestOperationInput, RequestBroadcastInput, PermissionInfo, - PermissionEntity + PermissionEntity, + ProofOfEventChallengeRequest, + ProofOfEventChallengeResponse, + ProofOfEventChallengeRecordedRequest } export { @@ -219,17 +233,21 @@ export { AcknowledgeResponseInput, ErrorResponseInput, PermissionResponseOutput, + ProofOfEventChallengeResponseOutput, SignPayloadResponseOutput, // EncryptPayloadResponseOutput, OperationResponseOutput, BroadcastResponseOutput, PermissionRequestInput, SignPayloadRequestInput, + ProofOfEventChallengeRequestInput, // EncryptPayloadRequestInput, OperationRequestInput, BroadcastRequestInput, PermissionRequestOutput, SignPayloadRequestOutput, + ProofOfEventChallengeRequestOutput, + ProofOfEventChallengeRecordedMessageOutput, // EncryptPayloadRequestOutput, OperationRequestOutput, BroadcastRequestOutput, diff --git a/packages/beacon-types/src/types/AccountInfo.ts b/packages/beacon-types/src/types/AccountInfo.ts index d74926eb8..45a0ab538 100644 --- a/packages/beacon-types/src/types/AccountInfo.ts +++ b/packages/beacon-types/src/types/AccountInfo.ts @@ -12,7 +12,10 @@ export interface AccountInfo extends PermissionEntity { id: string } walletKey?: string - publicKey: string + publicKey?: string connectedAt: number notification?: Notification + hasVerifiedChallenge?: boolean + walletType: 'implicit' | 'abstracted_account' + verificationType?: 'proof_of_event' } diff --git a/packages/beacon-types/src/types/PermissionInfo.ts b/packages/beacon-types/src/types/PermissionInfo.ts index 55c81f44e..82b39e786 100644 --- a/packages/beacon-types/src/types/PermissionInfo.ts +++ b/packages/beacon-types/src/types/PermissionInfo.ts @@ -6,6 +6,6 @@ export interface PermissionInfo extends PermissionEntity { senderId: string appMetadata: AppMetadata website: string - publicKey: string + publicKey?: string connectedAt: number } diff --git a/packages/beacon-types/src/types/RequestProofOfEventChallengeInput.ts b/packages/beacon-types/src/types/RequestProofOfEventChallengeInput.ts new file mode 100644 index 000000000..a74011fa2 --- /dev/null +++ b/packages/beacon-types/src/types/RequestProofOfEventChallengeInput.ts @@ -0,0 +1,7 @@ +/** + * @category DApp + */ +export interface RequestProofOfEventChallengeInput { + dAppChallengeId: string + payload: string +} diff --git a/packages/beacon-types/src/types/beacon/BeaconMessage.ts b/packages/beacon-types/src/types/beacon/BeaconMessage.ts index f05733e63..daf1b45bc 100644 --- a/packages/beacon-types/src/types/beacon/BeaconMessage.ts +++ b/packages/beacon-types/src/types/beacon/BeaconMessage.ts @@ -12,6 +12,9 @@ import { AcknowledgeResponse, DisconnectMessage, ErrorResponse, + ProofOfEventChallengeRequest, + ProofOfEventChallengeResponse, + ProofOfEventChallengeRecordedRequest, ChangeAccountRequest } from '@airgap/beacon-types' @@ -21,6 +24,9 @@ import { export type BeaconMessage = | PermissionRequest | PermissionResponse + | ProofOfEventChallengeRequest + | ProofOfEventChallengeResponse + | ProofOfEventChallengeRecordedRequest | OperationRequest | OperationResponse | SignPayloadRequest diff --git a/packages/beacon-types/src/types/beacon/BeaconMessageType.ts b/packages/beacon-types/src/types/beacon/BeaconMessageType.ts index 6eb8faa17..d71f92734 100644 --- a/packages/beacon-types/src/types/beacon/BeaconMessageType.ts +++ b/packages/beacon-types/src/types/beacon/BeaconMessageType.ts @@ -10,6 +10,9 @@ export enum BeaconMessageType { PermissionResponse = 'permission_response', SignPayloadResponse = 'sign_payload_response', // EncryptPayloadResponse = 'encrypt_payload_response', + ProofOfEventChallengeRequest = 'proof_of_event_challenge_request', + ProofOfEventChallengeResponse = 'proof_of_event_challenge_response', + ProofOfEventChallengeRecorded = 'proof_of_event_challenge_recorded', OperationResponse = 'operation_response', BroadcastResponse = 'broadcast_response', Acknowledge = 'acknowledge', diff --git a/packages/beacon-types/src/types/beacon/BeaconRequestMessage.ts b/packages/beacon-types/src/types/beacon/BeaconRequestMessage.ts index f0cedfec7..43062f7a3 100644 --- a/packages/beacon-types/src/types/beacon/BeaconRequestMessage.ts +++ b/packages/beacon-types/src/types/beacon/BeaconRequestMessage.ts @@ -2,7 +2,9 @@ import { PermissionRequest, OperationRequest, SignPayloadRequest, - BroadcastRequest + BroadcastRequest, + ProofOfEventChallengeRequest, + ProofOfEventChallengeRecordedRequest // EncryptPayloadRequest } from '@airgap/beacon-types' @@ -15,3 +17,5 @@ export type BeaconRequestMessage = | SignPayloadRequest // | EncryptPayloadRequest | BroadcastRequest + | ProofOfEventChallengeRequest + | ProofOfEventChallengeRecordedRequest diff --git a/packages/beacon-types/src/types/beacon/messages/BeaconRequestInputMessage.ts b/packages/beacon-types/src/types/beacon/messages/BeaconRequestInputMessage.ts index 393020803..90111708d 100644 --- a/packages/beacon-types/src/types/beacon/messages/BeaconRequestInputMessage.ts +++ b/packages/beacon-types/src/types/beacon/messages/BeaconRequestInputMessage.ts @@ -1,8 +1,9 @@ -import { Optional } from '@airgap/beacon-types' +import { Optional, ProofOfEventChallengeRecordedRequest } from '@airgap/beacon-types' import { PermissionRequest, OperationRequest, SignPayloadRequest, + ProofOfEventChallengeRequest, // EncryptPayloadRequest, BroadcastRequest } from '@airgap/beacon-types' @@ -18,6 +19,22 @@ export type IgnoredRequestInputProperties = 'id' | 'senderId' | 'version' * @category DApp */ export type PermissionRequestInput = Optional +/** + * @internalapi + * @category DApp + */ +export type ProofOfEventChallengeRequestInput = Optional< + ProofOfEventChallengeRequest, + IgnoredRequestInputProperties +> +/** + * @internalapi + * @category DApp + */ +export type ProofOfEventChallengeRecordedMessageInput = Optional< + ProofOfEventChallengeRecordedRequest, + IgnoredRequestInputProperties +> /** * @internalapi * @category DApp @@ -52,3 +69,5 @@ export type BeaconRequestInputMessage = // | EncryptPayloadRequestInput | SignPayloadRequestInput | BroadcastRequestInput + | ProofOfEventChallengeRequestInput + | ProofOfEventChallengeRecordedMessageInput diff --git a/packages/beacon-types/src/types/beacon/messages/BeaconRequestOutputMessage.ts b/packages/beacon-types/src/types/beacon/messages/BeaconRequestOutputMessage.ts index 394fc9361..65659f6e5 100644 --- a/packages/beacon-types/src/types/beacon/messages/BeaconRequestOutputMessage.ts +++ b/packages/beacon-types/src/types/beacon/messages/BeaconRequestOutputMessage.ts @@ -1,4 +1,8 @@ -import { Optional } from '@airgap/beacon-types' +import { + Optional, + ProofOfEventChallengeRecordedRequest, + ProofOfEventChallengeRequest +} from '@airgap/beacon-types' import { AppMetadata, PermissionRequest, @@ -28,6 +32,22 @@ export type PermissionRequestOutput = Optional & + ExtraResponseOutputProperties +/** + * @category Wallet + */ +export type ProofOfEventChallengeRecordedMessageOutput = Optional< + ProofOfEventChallengeRecordedRequest, + IgnoredRequestOutputProperties +> & + ExtraResponseOutputProperties +/** + * @category Wallet + */ export type OperationRequestOutput = Optional & ExtraResponseOutputProperties /** @@ -62,3 +82,5 @@ export type BeaconRequestOutputMessage = | SignPayloadRequestOutput // | EncryptPayloadRequestOutput | BroadcastRequestOutput + | ProofOfEventChallengeRequestOutput + | ProofOfEventChallengeRecordedMessageOutput diff --git a/packages/beacon-types/src/types/beacon/messages/BeaconResponseInputMessage.ts b/packages/beacon-types/src/types/beacon/messages/BeaconResponseInputMessage.ts index 0259ebf82..67dab30a9 100644 --- a/packages/beacon-types/src/types/beacon/messages/BeaconResponseInputMessage.ts +++ b/packages/beacon-types/src/types/beacon/messages/BeaconResponseInputMessage.ts @@ -1,4 +1,4 @@ -import { Optional } from '@airgap/beacon-types' +import { Optional, ProofOfEventChallengeResponse } from '@airgap/beacon-types' import { PermissionResponse, OperationResponse, @@ -21,6 +21,13 @@ export type PermissionResponseInput = Optional< PermissionResponse, IgnoredResponseInputProperties | 'appMetadata' > +/** + * @category Wallet + */ +export type ProofOfEventChallengeResponseInput = Optional< + ProofOfEventChallengeResponse, + IgnoredResponseInputProperties +> /** * @category Wallet */ @@ -61,3 +68,4 @@ export type BeaconResponseInputMessage = | BroadcastResponseInput | AcknowledgeResponseInput | ErrorResponseInput + | ProofOfEventChallengeResponseInput diff --git a/packages/beacon-types/src/types/beacon/messages/BeaconResponseOutputMessage.ts b/packages/beacon-types/src/types/beacon/messages/BeaconResponseOutputMessage.ts index 8d71fa2a7..7b8fe476a 100644 --- a/packages/beacon-types/src/types/beacon/messages/BeaconResponseOutputMessage.ts +++ b/packages/beacon-types/src/types/beacon/messages/BeaconResponseOutputMessage.ts @@ -4,7 +4,8 @@ import { SignPayloadResponse, // EncryptPayloadResponse, BroadcastResponse, - AccountInfo + AccountInfo, + ProofOfEventChallengeResponse } from '@airgap/beacon-types' /** @@ -20,6 +21,12 @@ export type PermissionResponseOutput = PermissionResponse & { accountInfo: AccountInfo walletKey?: string | undefined // Last selected wallet key } + +/** + * @category DApp + */ +export type ProofOfEventChallengeResponseOutput = ProofOfEventChallengeResponse + /** * @category DApp */ @@ -47,3 +54,4 @@ export type BeaconResponseOutputMessage = | SignPayloadResponseOutput // | EncryptPayloadResponseOutput | BroadcastResponseOutput + | ProofOfEventChallengeResponseOutput diff --git a/packages/beacon-types/src/types/beacon/messages/ChangeAccountRequest.ts b/packages/beacon-types/src/types/beacon/messages/ChangeAccountRequest.ts index d3817c7fa..9eb31ed93 100644 --- a/packages/beacon-types/src/types/beacon/messages/ChangeAccountRequest.ts +++ b/packages/beacon-types/src/types/beacon/messages/ChangeAccountRequest.ts @@ -1,11 +1,20 @@ -import { BeaconBaseMessage, BeaconMessageType, Network, PermissionScope, Threshold } from "@airgap/beacon-types" +import { + BeaconBaseMessage, + BeaconMessageType, + Network, + PermissionScope, + Threshold +} from '@airgap/beacon-types' import { Notification } from '../../Notification' export interface ChangeAccountRequest extends BeaconBaseMessage { - type: BeaconMessageType.ChangeAccountRequest - publicKey: string - network: Network - scopes: PermissionScope[] - threshold?: Threshold - notification?: Notification -} \ No newline at end of file + type: BeaconMessageType.ChangeAccountRequest + address?: string + walletType: 'implicit' | 'abstracted_account' + verificationType?: 'proof_of_event' + publicKey?: string + network: Network + scopes: PermissionScope[] + threshold?: Threshold + notification?: Notification +} diff --git a/packages/beacon-types/src/types/beacon/messages/PermissionResponse.ts b/packages/beacon-types/src/types/beacon/messages/PermissionResponse.ts index 2042a52d3..b2128ef82 100644 --- a/packages/beacon-types/src/types/beacon/messages/PermissionResponse.ts +++ b/packages/beacon-types/src/types/beacon/messages/PermissionResponse.ts @@ -12,9 +12,12 @@ import { Notification } from '../../Notification' * @category Message */ export interface PermissionResponse extends BeaconBaseMessage { + address?: string // If wallet is an abstracted_account, address should be defined + walletType: 'implicit' | 'abstracted_account' + verificationType?: 'proof_of_event' type: BeaconMessageType.PermissionResponse appMetadata: AppMetadata // Some additional information about the Wallet - publicKey: string // Public Key, because it can be used to verify signatures + publicKey?: string // Public Key, because it can be used to verify signatures. Undefined if wallet is an abstracted_account network: Network // Network on which the permissions have been granted scopes: PermissionScope[] // Permissions that have been granted for this specific address / account threshold?: Threshold diff --git a/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeRecordedRequest.ts b/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeRecordedRequest.ts new file mode 100644 index 000000000..4a99f3763 --- /dev/null +++ b/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeRecordedRequest.ts @@ -0,0 +1,8 @@ +import { BeaconBaseMessage, BeaconMessageType } from '@airgap/beacon-types' + +export interface ProofOfEventChallengeRecordedRequest extends BeaconBaseMessage { + type: BeaconMessageType.ProofOfEventChallengeRecorded + dAppChallengeId: string // dApp decided challenge identifier + success: boolean // Indicating whether the challenge is recorded successfully + errorMessage: string // Optional, error message incase of failure +} diff --git a/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeRequest.ts b/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeRequest.ts new file mode 100644 index 000000000..8fbf82ea1 --- /dev/null +++ b/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeRequest.ts @@ -0,0 +1,8 @@ +import { BeaconBaseMessage, BeaconMessageType } from '@airgap/beacon-types' + +export interface ProofOfEventChallengeRequest extends BeaconBaseMessage { + type: BeaconMessageType.ProofOfEventChallengeRequest + payload: string // The payload that will be emitted. + contractAddress: string // The contract address of the abstracted account + dAppChallengeId: string // dApp decided challenge identifier +} diff --git a/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeResponse.ts b/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeResponse.ts new file mode 100644 index 000000000..68ea89221 --- /dev/null +++ b/packages/beacon-types/src/types/beacon/messages/ProofOfEventChallengeResponse.ts @@ -0,0 +1,7 @@ +import { BeaconBaseMessage, BeaconMessageType } from '@airgap/beacon-types' + +export interface ProofOfEventChallengeResponse extends BeaconBaseMessage { + type: BeaconMessageType.ProofOfEventChallengeResponse + dAppChallengeId: string // dApp decided challenge identifier + isAccepted: boolean // Indicating whether the challenge is accepted +} diff --git a/packages/beacon-types/src/types/storage/StorageKey.ts b/packages/beacon-types/src/types/storage/StorageKey.ts index 0e0193e44..bc3aba932 100644 --- a/packages/beacon-types/src/types/storage/StorageKey.ts +++ b/packages/beacon-types/src/types/storage/StorageKey.ts @@ -14,6 +14,7 @@ export enum StorageKey { BEACON_SDK_SECRET_SEED = 'beacon:sdk-secret-seed', APP_METADATA_LIST = 'beacon:app-metadata-list', PERMISSION_LIST = 'beacon:permissions', + ONGOING_PROOF_OF_EVENT_CHALLENGES = 'beacon:ongoing-proof-of-event-challenges', BEACON_SDK_VERSION = 'beacon:sdk_version', MATRIX_PRESERVED_STATE = 'beacon:sdk-matrix-preserved-state', MATRIX_PEER_ROOM_IDS = 'beacon:matrix-peer-rooms', diff --git a/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts b/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts index e98842d34..43c72b64f 100644 --- a/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts +++ b/packages/beacon-types/src/types/storage/StorageKeyReturnDefaults.ts @@ -21,6 +21,7 @@ export const defaultValues: StorageKeyReturnDefaults = { [StorageKey.BEACON_SDK_SECRET_SEED]: undefined, [StorageKey.APP_METADATA_LIST]: [], [StorageKey.PERMISSION_LIST]: [], + [StorageKey.ONGOING_PROOF_OF_EVENT_CHALLENGES]: [], [StorageKey.BEACON_SDK_VERSION]: undefined, [StorageKey.MATRIX_PRESERVED_STATE]: {}, [StorageKey.MATRIX_PEER_ROOM_IDS]: {}, diff --git a/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts b/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts index 08450234b..2cb4f7d67 100644 --- a/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts +++ b/packages/beacon-types/src/types/storage/StorageKeyReturnType.ts @@ -5,7 +5,8 @@ import { P2PPairingRequest, AppMetadata, PermissionInfo, - ExtendedWalletConnectPairingResponse + ExtendedWalletConnectPairingResponse, + RequestProofOfEventChallengeInput } from '../..' // TODO: MOVE TYPE import { MatrixState } from '../../matrix-client/MatrixClientStore' import { ExtendedP2PPairingResponse } from '../P2PPairingResponse' @@ -29,6 +30,10 @@ export interface StorageKeyReturnType { [StorageKey.BEACON_SDK_SECRET_SEED]: string | undefined [StorageKey.APP_METADATA_LIST]: AppMetadata[] [StorageKey.PERMISSION_LIST]: PermissionInfo[] + [StorageKey.ONGOING_PROOF_OF_EVENT_CHALLENGES]: ({ + contractAddress: string + accountIdentifier: string + } & RequestProofOfEventChallengeInput)[] [StorageKey.BEACON_SDK_VERSION]: string | undefined [StorageKey.MATRIX_PRESERVED_STATE]: { [key: string]: unknown } // TODO: TYPE Partial [StorageKey.MATRIX_PEER_ROOM_IDS]: { [key: string]: string | undefined } diff --git a/packages/beacon-utils/src/index.ts b/packages/beacon-utils/src/index.ts index 5d3197b11..a79f06d0d 100644 --- a/packages/beacon-utils/src/index.ts +++ b/packages/beacon-utils/src/index.ts @@ -11,9 +11,11 @@ export { openCryptobox, recipientString, signMessage, + isValidAddress, prefixPublicKey } from './utils/crypto' export { generateGUID } from './utils/generate-uuid' +export const CONTRACT_PREFIX = 'KT1' export const secretbox_NONCEBYTES = 24 // crypto_secretbox_NONCEBYTES export const secretbox_MACBYTES = 16 // crypto_secretbox_MACBYTES diff --git a/packages/beacon-utils/src/utils/crypto.ts b/packages/beacon-utils/src/utils/crypto.ts index 9c5c03dad..a6834c488 100644 --- a/packages/beacon-utils/src/utils/crypto.ts +++ b/packages/beacon-utils/src/utils/crypto.ts @@ -262,4 +262,20 @@ export const signMessage = async ( return signature } +export const isValidAddress = (address: string): boolean => { + const prefixes = ['tz1', 'tz2', 'tz3', 'tz4', 'KT1', 'txr1', 'sr1'] + + if (!prefixes.some((p) => address.toLowerCase().startsWith(p.toLowerCase()))) { + return false + } + + try { + bs58check.decode(address) + } catch (error) { + return false + } + + return true +} + /* eslint-enable prefer-arrow/prefer-arrow-functions */ diff --git a/packages/beacon-wallet/src/client/WalletClient.ts b/packages/beacon-wallet/src/client/WalletClient.ts index d99a2107d..eb2bac255 100644 --- a/packages/beacon-wallet/src/client/WalletClient.ts +++ b/packages/beacon-wallet/src/client/WalletClient.ts @@ -84,7 +84,6 @@ export class WalletClient extends Client { public async init(): Promise { const keyPair = await this.keyPair // We wait for keypair here so the P2P Transport creation is not delayed and causing issues - const p2pTransport = new WalletP2PTransport( this.name, keyPair, diff --git a/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts b/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts index 9b55a320e..18ee73f73 100644 --- a/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts +++ b/packages/beacon-wallet/src/interceptors/IncomingRequestInterceptor.ts @@ -6,12 +6,14 @@ import { OperationRequestOutput, SignPayloadRequestOutput, BroadcastRequestOutput, + ProofOfEventChallengeRequestOutput, ConnectionContext, BeaconRequestMessage, BeaconMessageWrapper, BlockchainRequestV3, PermissionRequestV3, - BeaconBaseMessage + BeaconBaseMessage, + ProofOfEventChallengeRecordedMessageOutput // EncryptPayloadRequestOutput } from '@airgap/beacon-types' import { AppMetadataManager, Logger } from '@airgap/beacon-core' @@ -141,7 +143,32 @@ export class IncomingRequestInterceptor { interceptorCallback(request, connectionInfo) } break - + case BeaconMessageType.ProofOfEventChallengeRequest: + { + const appMetadata: AppMetadata = await IncomingRequestInterceptor.getAppMetadata( + appMetadataManager, + message.senderId + ) + const request: ProofOfEventChallengeRequestOutput = { + appMetadata, + ...message + } + interceptorCallback(request, connectionInfo) + } + break + case BeaconMessageType.ProofOfEventChallengeRecorded: + { + const appMetadata: AppMetadata = await IncomingRequestInterceptor.getAppMetadata( + appMetadataManager, + message.senderId + ) + const request: ProofOfEventChallengeRecordedMessageOutput = { + appMetadata, + ...message + } + interceptorCallback(request, connectionInfo) + } + break default: logger.log('intercept', 'Message not handled') assertNever(message) diff --git a/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts b/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts index 9528e1f8c..bcbac30d5 100644 --- a/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts +++ b/packages/beacon-wallet/src/interceptors/OutgoingResponseInterceptor.ts @@ -21,10 +21,11 @@ import { BeaconMessageWrapper, BlockchainResponseV3, PermissionResponseV3, - BeaconBaseMessage + BeaconBaseMessage, + ProofOfEventChallengeResponse // EncryptPayloadResponse } from '@airgap/beacon-types' -import { getAddressFromPublicKey } from '@airgap/beacon-utils' +import { getAddressFromPublicKey, CONTRACT_PREFIX, isValidAddress } from '@airgap/beacon-utils' interface OutgoingResponseInterceptorOptions { senderId: string @@ -183,10 +184,29 @@ export class OutgoingResponseInterceptor { ...message } + if (!response.address && !response.publicKey) { + throw new Error('Address or PublicKey must be defined') + } + const publicKey = response.publicKey - const address: string = await getAddressFromPublicKey(publicKey) + const address: string = response.address ?? (await getAddressFromPublicKey(publicKey!)) + + if (isValidAddress(address)) { + throw new Error(`Invalid address: "${address}"`) + } + + if ( + message.walletType === 'abstracted_account' && + address.substring(0, 3) !== CONTRACT_PREFIX + ) { + throw new Error( + `Invalid abstracted account address "${address}", it should be a ${CONTRACT_PREFIX} address` + ) + } + const appMetadata = await appMetadataManager.getAppMetadata(request.senderId) + if (!appMetadata) { throw new Error('AppMetadata not found') } @@ -249,7 +269,16 @@ export class OutgoingResponseInterceptor { interceptorCallback(response) } break - + case BeaconMessageType.ProofOfEventChallengeResponse: + { + const response: ProofOfEventChallengeResponse = { + senderId, + version: '2', + ...message + } + interceptorCallback(response) + } + break default: logger.log('intercept', 'Message not handled') assertNever(message)