From 5f925a16b88ba842f7df8f77925e38ba80599403 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Thu, 19 Jan 2023 10:59:28 -0300 Subject: [PATCH 01/44] Update SDK to v2.0.0 --- packages/extension/package.json | 2 +- packages/test-project/package.json | 2 +- packages/ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/extension/package.json b/packages/extension/package.json index fd1f4b4d..7af92f09 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -20,7 +20,7 @@ "webpack-cli": "^4.9.0" }, "dependencies": { - "algosdk": "1.24.1", + "algosdk": "2.0.0", "buffer": "^6.0.3" }, "scripts": { diff --git a/packages/test-project/package.json b/packages/test-project/package.json index ca801c49..935b7404 100644 --- a/packages/test-project/package.json +++ b/packages/test-project/package.json @@ -5,7 +5,7 @@ "license": "MIT", "description": "Repository for tests", "devDependencies": { - "algosdk": "1.24.1", + "algosdk": "2.0.0", "jest": "^28.1.0", "jest-runner-groups": "^2.2.0", "puppeteer": "^13.7.0", diff --git a/packages/ui/package.json b/packages/ui/package.json index 25a7ce1b..d5eb5238 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -16,7 +16,7 @@ "@fortawesome/fontawesome-free": "^5.15.0", "@ledgerhq/hw-app-algorand": "^6.27.9", "@ledgerhq/hw-transport-webhid": "^6.27.9", - "algosdk": "1.24.1", + "algosdk": "2.0.0", "buffer": "^6.0.3", "history": "^5.0.0", "htm": "^3.0.4", From da06d5767a87dab749388ddc429b4b2df62ebbea Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Fri, 20 Jan 2023 15:05:07 -0300 Subject: [PATCH 02/44] Fix the post error for single transactions --- packages/common/src/errors.ts | 12 +- .../src/background/messaging/task.ts | 15 +- .../test-project/tests/dapp-arcs-misc.test.js | 299 ++++++++++-------- 3 files changed, 182 insertions(+), 144 deletions(-) diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts index 9f90ab4d..bde76762 100644 --- a/packages/common/src/errors.ts +++ b/packages/common/src/errors.ts @@ -133,8 +133,14 @@ export class RequestError { 'All transactions need to belong to the same ledger.', 4300 ); - static PartiallySuccessfulPost = (successTxnIDs: string[], data: any): PostError => - new PostError( + static FailedPost = (data: any): RequestError => + new RequestError( + "The transaction was unable to be posted. The reason returned by the network is found inside the 'data' property.", + 4400, + data + ); + static PartiallySuccessfulPost = (successTxnIDs: string[], data: any): PartialPostError => + new PartialPostError( successTxnIDs, "Some of the groups of transactions were unable to be posted. The reason for each unsuccessful group is in it's corresponding position inside the 'data' array.", 4400, @@ -157,7 +163,7 @@ export class RequestError { } } -class PostError extends RequestError { +class PartialPostError extends RequestError { successTxnIDs: string[]; public constructor(successTxnIDs: string[], message: string, code: number, data?: any) { diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index c36b4eef..6e38339a 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -966,7 +966,9 @@ export class Task { const txID = res.value.txId; confirmationPromises[index] = algosdk.waitForConfirmation(algod, txID, 2); } else { - fetchErrors[index] = res.reason.message; + logging.log('Failed post response:', LogLevel.Debug); + logging.log(res, LogLevel.Debug); + fetchErrors[index] = res.reason; fetchResponses[index] = null; } }); @@ -990,9 +992,14 @@ export class Task { // Check if there's any non-null errors if (fetchErrors.filter(Boolean).length) { const successTxnIDs = fetchResponses; - const reasons = new Array(groupsToSend.length); - fetchErrors.forEach((e, i) => (reasons[i] = e ? e.message : null)); - d.error = RequestError.PartiallySuccessfulPost(successTxnIDs, reasons); + if (groupsToSend.length > 1) { + const reasons = new Array(groupsToSend.length); + fetchErrors.forEach((e, i) => (reasons[i] = e ? e.message : null)); + d.error = RequestError.PartiallySuccessfulPost(successTxnIDs, reasons); + } else { + const reason = fetchErrors[0].message; + d.error = RequestError.FailedPost(reason); + } resolve(d); } else { d.response = { txnIDs: fetchResponses }; diff --git a/packages/test-project/tests/dapp-arcs-misc.test.js b/packages/test-project/tests/dapp-arcs-misc.test.js index 05d3e5b0..8d833dcf 100644 --- a/packages/test-project/tests/dapp-arcs-misc.test.js +++ b/packages/test-project/tests/dapp-arcs-misc.test.js @@ -20,18 +20,20 @@ const uiAccount = accounts.ui; let sdkParams; -const getBasicSdkTxn = () => buildSdkTx({ - type: 'pay', - from: uiAccount.address, - to: uiAccount.address, - amount: Math.ceil(Math.random() * 100), - ...sdkParams, - fee: 1000, -}); +const getBasicSdkTxn = () => + buildSdkTx({ + type: 'pay', + from: uiAccount.address, + to: uiAccount.address, + amount: Math.ceil(Math.random() * 100), + ...sdkParams, + fee: 1000, + }); const getSdkTxnArray = () => [getBasicSdkTxn(), getBasicSdkTxn()]; -const signWithSDK = (tx) => byteArrayToBase64(tx.signTxn(algosdk.mnemonicToSecretKey(uiAccount.mnemonic).sk)); +const signWithSDK = (tx) => + byteArrayToBase64(tx.signTxn(algosdk.mnemonicToSecretKey(uiAccount.mnemonic).sk)); describe('Wallet Setup', () => { beforeAll(async () => { @@ -49,159 +51,182 @@ describe('Wallet Setup', () => { }); describe('PostTxns Validations', () => { - test('Error on missing group(s)', async () => { - const sdkTxns = getSdkTxnArray(); - const signedTxns = sdkTxns.map(signWithSDK); - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.postTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, signedTxns) - ).resolves.toMatchObject({ - message: expect.stringContaining('reasons are provided'), - code: 4300, - data: expect.stringContaining('same group') + describe('Transaction validations', () => { + test('Error on missing group(s)', async () => { + const sdkTxns = getSdkTxnArray(); + const signedTxns = sdkTxns.map(signWithSDK); + await expect( + dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.postTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, signedTxns) + ).resolves.toMatchObject({ + message: expect.stringContaining('reasons are provided'), + code: 4300, + data: expect.stringContaining('same group'), + }); }); - }); + test('Error on unordered group(s)', async () => { + const sdkTxns = algosdk.assignGroupID(getSdkTxnArray()); + const signedTxns = sdkTxns.map(signWithSDK); + const unorderedTxns = [signedTxns[1], signedTxns[0]]; + await expect( + dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.postTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unorderedTxns) + ).resolves.toMatchObject({ + message: expect.stringContaining('reasons are provided'), + code: 4300, + data: expect.stringContaining('different order'), + }); + }); - test('Error on unordered group(s)', async () => { - const sdkTxns = algosdk.assignGroupID(getSdkTxnArray()); - const unsignedTxns = sdkTxns.map(signWithSDK); - const unorderedTxns = [unsignedTxns[1], unsignedTxns[0]]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.postTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unorderedTxns) - ).resolves.toMatchObject({ - message: expect.stringContaining('reasons are provided'), - code: 4300, - data: expect.stringContaining('different order') + test('Error on incomplete group(s)', async () => { + const sdkTxns = algosdk.assignGroupID(getSdkTxnArray()); + const signedTxns = sdkTxns.map(signWithSDK); + const incompleteTxns = [signedTxns.shift()]; + await expect( + dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.postTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, incompleteTxns) + ).resolves.toMatchObject({ + message: expect.stringContaining('reasons are provided'), + code: 4300, + data: expect.stringContaining('group is incomplete'), + }); }); }); - - test('Error on incomplete group(s)', async () => { - const sdkTxns = algosdk.assignGroupID(getSdkTxnArray()); - const unsignedTxns = sdkTxns.map(signWithSDK); - const incompleteTxns = [unsignedTxns.shift()]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.postTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, incompleteTxns) - ).resolves.toMatchObject({ - message: expect.stringContaining('reasons are provided'), - code: 4300, - data: expect.stringContaining('group is incomplete') + describe('Network validations', () => { + test('Error on failed post', async () => { + const noFeeTxn = getBasicSdkTxn(); + noFeeTxn.fee = 0; + console.log(noFeeTxn); + const sdkTxns = [noFeeTxn]; + const signedTxns = sdkTxns.map(signWithSDK); + await expect( + dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.postTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, signedTxns) + ).resolves.toMatchObject({ + message: expect.stringContaining('unable to be posted. The reason'), + code: 4400, + data: expect.stringContaining('had 0 in fees'), + }); }); - }); - - jest.setTimeout(20000); - - test('Partially succeded post', async () => { - async function signTxnGroups(transactionsToSign) { - return await dappPage.evaluate( - async (transactionsToSign) => { - const signPromise = algorand.signTxns(transactionsToSign) + + jest.setTimeout(25000); + + test('Partially succeded post', async () => { + async function signTxnGroups(transactionsToSign) { + return await dappPage.evaluate(async (transactionsToSign) => { + const signPromise = algorand + .signTxns(transactionsToSign) .then((data) => { return data; }) .catch((error) => { return error; }); - - const amountOfGroups = Array.isArray(transactionsToSign[0]) ? transactionsToSign.length : 1; + + const amountOfGroups = Array.isArray(transactionsToSign[0]) + ? transactionsToSign.length + : 1; await window['authorizeSignTxnGroups'](amountOfGroups); - + return await Promise.resolve(signPromise); - }, - transactionsToSign, - ); - } - - const noFeeTx = buildSdkTx({ - type: 'pay', - from: uiAccount.address, - to: uiAccount.address, - amount: Math.ceil(Math.random() * 100), - ...sdkParams, - fee: 0, - flatFee: true, + }, transactionsToSign); + } + + const noFeeTx = buildSdkTx({ + type: 'pay', + from: uiAccount.address, + to: uiAccount.address, + amount: Math.ceil(Math.random() * 100), + ...sdkParams, + fee: 0, + flatFee: true, + }); + + const sdkTxns = [noFeeTx, getBasicSdkTxn()]; + const unsignedTxns = sdkTxns.map(prepareWalletTx); + const nestedTxns = unsignedTxns.map((tx) => [tx]); + await extensionPage.waitForTimeout(2000); + const signedTxns = await signTxnGroups(nestedTxns); + const postResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.postTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, signedTxns); + + const txID = algosdk.decodeSignedTransaction(base64ToByteArray(signedTxns[1][0])).txn.txID(); + await expect(postResponse).toMatchObject({ + message: expect.stringContaining('unsuccessful group'), + code: 4400, + data: expect.anything(), + successTxnIDs: expect.anything(), + }); + await expect(postResponse.successTxnIDs).toHaveLength(2); + await expect(postResponse.successTxnIDs[1]).toHaveLength(1); + await expect(postResponse.successTxnIDs[1]).toContain(txID); + await expect(postResponse.data).toHaveLength(2); }); - const sdkTxns = [noFeeTx, getBasicSdkTxn()]; - const unsignedTxns = sdkTxns.map(prepareWalletTx); - const nestedTxns = unsignedTxns.map((tx) => [tx]); - await extensionPage.waitForTimeout(2000); - const signedTxns = await signTxnGroups(nestedTxns); - const postResponse = await dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.postTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, signedTxns); - - const txID = algosdk.decodeSignedTransaction(base64ToByteArray(signedTxns[1][0])).txn.txID(); - await expect(postResponse).toMatchObject({ - message: expect.stringContaining('unsuccessful group'), - code: 4400, - data: expect.anything(), - successTxnIDs: expect.anything(), - }); - await expect(postResponse.successTxnIDs).toHaveLength(2); - await expect(postResponse.successTxnIDs[1]).toHaveLength(1); - await expect(postResponse.successTxnIDs[1]).toContain(txID); - await expect(postResponse.data).toHaveLength(2); - }); + jest.setTimeout(30000); - test('Sign and Send commits txs to the network', async () => { - async function signAndPostTxns(transactionsToSign) { - return await dappPage.evaluate( - async (transactionsToSign) => { - console.log('before sign'); - const signPostPromise = algorand.signAndPostTxns(transactionsToSign) + test('Sign and Send commits txs to the network', async () => { + async function signAndPostTxns(transactionsToSign) { + return await dappPage.evaluate(async (transactionsToSign) => { + const signPostPromise = algorand + .signAndPostTxns(transactionsToSign) .then((data) => { return data; }) .catch((error) => { return error; }); - + await window['authorizeSignTxn'](); return await Promise.resolve(signPostPromise); - }, - transactionsToSign, - ); - } - - const sdkTxn = getBasicSdkTxn(); - const unsignedTxns = [sdkTxn].map(prepareWalletTx); - const txID = sdkTxn.txID(); - await extensionPage.waitForTimeout(2000); - const signPostResponse = await signAndPostTxns(unsignedTxns); - await expect(signPostResponse).toMatchObject({ - txnIDs: expect.arrayContaining([txID]), + }, transactionsToSign); + } + + const sdkTxn = getBasicSdkTxn(); + const unsignedTxns = [sdkTxn].map(prepareWalletTx); + const txID = sdkTxn.txID(); + await extensionPage.waitForTimeout(2000); + const signPostResponse = await signAndPostTxns(unsignedTxns); + await expect(signPostResponse).toMatchObject({ + txnIDs: expect.arrayContaining([txID]), + }); }); - }); -}); \ No newline at end of file +}); From 16914aca2bc32f68baa581526708da09cdf86353 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Fri, 20 Jan 2023 15:59:01 -0300 Subject: [PATCH 03/44] Fix default ledger display on Enable --- packages/ui/src/pages/Enable.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/pages/Enable.ts b/packages/ui/src/pages/Enable.ts index 62a02f6a..8d765865 100644 --- a/packages/ui/src/pages/Enable.ts +++ b/packages/ui/src/pages/Enable.ts @@ -51,6 +51,7 @@ const Enable: FunctionalComponent = () => { restrictedLedgers = availableLedgers; } sessionLedgers = restrictedLedgers; + store.setLedger(restrictedLedgers[0].name); } }); From b759831d9182421cec60952b9ddf92d4b89f7027 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Fri, 20 Jan 2023 16:12:53 -0300 Subject: [PATCH 04/44] Fix origin checking for accounts && add network name --- .../src/background/messaging/task.ts | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index c36b4eef..326b1d53 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -63,13 +63,15 @@ export class Task { } // Checks for the originId authorization in details then call to make sure the account exists in Algosigner. - private static checkAccountIsImportedAndAuthorized(genesisID: string, address: string, originId: string): void { - // Legacy authorized and internal calls will not have an originId in authorized pool details - if (Task.authorized_pool_details[originId]) { + private static checkAccountIsImportedAndAuthorized(ledger: string, genesisID: string, genesisHash: string, address: string, origin: string): void { + // Legacy authorized and internal calls will not have an origin in authorized pool details + if (Task.authorized_pool_details[origin]) { // This must be a dApp using enable - verify the ledger and address are authorized - if ((Task.authorized_pool_details[originId]['genesisID'] !== genesisID) - || (!Task.authorized_pool_details[originId]['accounts'].includes(address))) { - throw RequestError.NoAccountMatch(address, genesisID); + if ((Task.authorized_pool_details[origin]['genesisID'] !== genesisID) + || (Task.authorized_pool_details[origin]['ledger'] !== ledger) + || (genesisHash && Task.authorized_pool_details[origin]['genesisHash'] !== genesisHash) + || (!Task.authorized_pool_details[origin]['accounts'].includes(address))) { + throw RequestError.NoAccountMatch(address, ledger); } } // Call the normal account check @@ -289,8 +291,9 @@ export class Task { processedTxArray[index] = processedTx; const wrap = getValidatedTxnWrap(processedTx, processedTx['type']); transactionWraps[index] = wrap; - const genesisID = wrap.transaction.genesisID; + const ledger = getLedgerFromMixedGenesis(wrap.transaction.genesisID, wrap.transaction.genesisHash); + const signers: Array = walletTransactions[index].signers; const signedTxn: string = walletTransactions[index].stxn; const msigData: MultisigMetadata = walletTransactions[index].msig; @@ -304,7 +307,7 @@ export class Task { if (!algosdk.isValidAddress(authAddr)) { throw RequestError.InvalidAuthAddress(authAddr); } - Task.checkAccountIsImportedAndAuthorized(genesisID, authAddr, request.originTabID); + Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisId, ledger.genesisHash, authAddr, request.origin); } // If we have msigData, we validate the addresses and fetch the resulting msig address @@ -369,7 +372,7 @@ export class Task { // We make sure we have the available accounts for signing signers.forEach((address) => { try { - Task.checkAccountIsImportedAndAuthorized(genesisID, address, request.originTabID); + Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisId, ledger.genesisHash, address, request.origin); } catch (e) { throw RequestError.CantMatchMsigSigners(e.message); } @@ -398,7 +401,7 @@ export class Task { } else { // There's no signers field, we validate the sender if there's no msig if (!msigData) { - Task.checkAccountIsImportedAndAuthorized(genesisID, wrap.transaction.from, request.originTabID); + Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisId, ledger.genesisHash, wrap.transaction.from, request.origin); } } @@ -1097,7 +1100,7 @@ export class Task { private: { // authorization-allow [JsonRpcMethod.AuthorizationAllow]: (d) => { - const { responseOriginTabID, isEnable, accounts, genesisID, genesisHash } = d.body.params; + const { responseOriginTabID, isEnable, accounts, genesisID, genesisHash, ledger } = d.body.params; const auth = Task.requests[responseOriginTabID]; const message = auth.message; @@ -1123,17 +1126,18 @@ export class Task { } if (rejectedAccounts.length > 0) { message.error = RequestError.EnableRejected({ 'accounts': rejectedAccounts }); - } - else { + } else { message.response = { 'genesisID': genesisID, 'genesisHash': genesisHash, accounts: sharedAccounts } + const poolDetails = { ...message.response, ledger: ledger } + // Add to the authorized pool details. // This will be checked to restrict access for enable function users - Task.authorized_pool_details[`${message.origin}`] = message.response; + Task.authorized_pool_details[`${message.origin}`] = poolDetails; } } MessageApi.send(message); From 23e279b1b0fa52f23b15c82c493b9dada7acc8c6 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Fri, 20 Jan 2023 16:25:19 -0300 Subject: [PATCH 05/44] Update error codes and descriptions --- packages/common/src/errors.ts | 14 +++++++------- .../extension/src/background/messaging/task.ts | 14 ++++++++------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts index 9f90ab4d..8a676dd7 100644 --- a/packages/common/src/errors.ts +++ b/packages/common/src/errors.ts @@ -18,10 +18,6 @@ export class RequestError { 4001, data ); - static SiteNotAuthorizedByUser = new RequestError( - 'The extension user has not authorized requests from this website.', - 4100 - ); static NoMnemonicAvailable = (address: string): RequestError => new RequestError( `The user does not possess the required private key to sign with for address: "${address}".`, @@ -32,7 +28,7 @@ export class RequestError { `No matching account found on AlgoSigner for address "${address}" on network ${ledger}.`, 4100 ); - static UnsupportedLedger = new RequestError('The provided ledger is not supported.', 4200); + static UnsupportedNetwork = new RequestError('The provided network is not supported.', 4200); static PendingTransaction = new RequestError('Another query processing', 4201); static LedgerMultipleGroups = new RequestError( 'Ledger hardware device signing is only available for one transaction group at a time.', @@ -44,6 +40,10 @@ export class RequestError { ); static AlgoSignerNotInitialized = new RequestError( 'AlgoSigner was not initialized properly beforehand.', + 4200 + ); + static SiteNotAuthorizedByUser = new RequestError( + 'The extension user has not authorized requests from this website.', 4202 ); static InvalidFields = (data?: any): RequestError => @@ -129,8 +129,8 @@ export class RequestError { 'All transactions provided in a same group need to have matching group IDs.', 4300 ); - static NoDifferentLedgers = new RequestError( - 'All transactions need to belong to the same ledger.', + static NoDifferentNetworks = new RequestError( + 'All transactions need to belong to the same network.', 4300 ); static PartiallySuccessfulPost = (successTxnIDs: string[], data: any): PostError => diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index 326b1d53..98531da3 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -485,10 +485,12 @@ export class Task { if (transactionWraps.length > 1) { if ( !transactionWraps.every( - (wrap) => transactionWraps[0].transaction.genesisID === wrap.transaction.genesisID + (wrap) => + transactionWraps[0].transaction.genesisID === wrap.transaction.genesisID || + transactionWraps[0].transaction.genesisHash === wrap.transaction.genesisHash ) ) { - throw RequestError.NoDifferentLedgers; + throw RequestError.NoDifferentNetworks; } if (!providedGroupId || !transactionWraps.every((wrap) => wrap.transaction.group)) { @@ -648,7 +650,7 @@ export class Task { // This is because a dapp may request an id and hash from different ledgers if ((genesisID && genesisID !== ledgerTemplate.genesisId) || (genesisHash && ledgerTemplate.genesisHash && genesisHash !== ledgerTemplate.genesisHash)) { - d.error = RequestError.UnsupportedLedger; + d.error = RequestError.UnsupportedNetwork; setTimeout(() => { MessageApi.send(d); }, 500); @@ -727,7 +729,7 @@ export class Task { // If we need a requested a ledger but don't have it, respond with an error if (walletAccounts === undefined) { - d.error = RequestError.UnsupportedLedger; + d.error = RequestError.UnsupportedNetwork; setTimeout(() => { MessageApi.send(d); @@ -1082,7 +1084,7 @@ export class Task { const accounts = session.wallet[d.body.params.ledger]; // If we have requested a ledger but don't have it, respond with an error if (accounts === undefined) { - d.error = RequestError.UnsupportedLedger; + d.error = RequestError.UnsupportedNetwork; reject(d); return; } @@ -1224,7 +1226,7 @@ export class Task { if (unlockedValue[ledger] === undefined) { delete Task.requests[responseOriginTabID]; - message.error = RequestError.UnsupportedLedger; + message.error = RequestError.UnsupportedNetwork; MessageApi.send(message); return; } From 6cd169789bb495fa92120ba80572213a784e3e70 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 25 Jan 2023 12:48:04 -0300 Subject: [PATCH 06/44] Better handle logged-out sessions on Enable --- .../src/background/messaging/task.ts | 268 +++++++----------- packages/ui/src/pages/Enable.ts | 62 ++-- packages/ui/src/pages/Login.ts | 8 +- 3 files changed, 140 insertions(+), 198 deletions(-) diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index 98531da3..071f04b0 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -33,7 +33,7 @@ const popupProperties = { export class Task { private static requests: { [key: string]: any } = {}; private static authorized_pool: Array = []; - private static authorized_pool_details: any = {} + private static authorized_pool_details: any = {}; public static isAuthorized(origin: string): boolean { return Task.authorized_pool.indexOf(origin) > -1; @@ -605,7 +605,7 @@ export class Task { }, // Enable function as defined in ARC-006 [JsonRpcMethod.EnableAuthorization]: (d: any) => { - const { accounts } = d.body.params; + const { accounts: requestedAccounts } = d.body.params; let { genesisID, genesisHash } = d.body.params; logging.log('Enable params:', LogLevel.Debug); logging.log(d.body.params, LogLevel.Debug); @@ -613,11 +613,6 @@ export class Task { // Delete any previous request made from the Tab that it's trying to connect. delete Task.requests[d.originTabID]; - // Get an internal session - if unavailable then we will connect and deny - const session = InternalMethods.getHelperSession(); - logging.log('Session:', LogLevel.Debug); - logging.log(session, LogLevel.Debug); - // Set a flag for a specified network let networkSpecifiedType = 0; if (genesisID && genesisHash) { @@ -627,158 +622,111 @@ export class Task { } d.body.params.networkSpecifiedType = networkSpecifiedType; - // If session is missing then we should throw an error, but still popup the login screen - if (session.availableLedgers.length === 0) { - // No ledgers are available. The user is logged out so just prompt them to login. - extensionBrowser.windows.create({ - url: extensionBrowser.runtime.getURL('index.html#/close'), - ...popupProperties, - }); - - // Let the dApp know there was an issue with a generic unauthorized - d.error = RequestError.SiteNotAuthorizedByUser; + // Get ledger/hash/id from the genesisID and/or hash + const ledgerTemplate = getLedgerFromMixedGenesis(genesisID, genesisHash); - // Set the timeout higher to allow for the previous popup before responding + // Validate that the genesis id and hash if provided match the resulting one + // This is because a dapp may request an id and hash from different ledgers + if ( + (genesisID && genesisID !== ledgerTemplate.genesisId) || + (genesisHash && + ledgerTemplate.genesisHash && + genesisHash !== ledgerTemplate.genesisHash) + ) { + d.error = RequestError.UnsupportedNetwork; setTimeout(() => { MessageApi.send(d); - }, 2000); - } else { - // Get ledger/hash/id from the genesisID and/or hash - const ledgerTemplate = getLedgerFromMixedGenesis(genesisID, genesisHash); - - // Validate that the genesis id and hash if provided match the resulting one - // This is because a dapp may request an id and hash from different ledgers - if ((genesisID && genesisID !== ledgerTemplate.genesisId) - || (genesisHash && ledgerTemplate.genesisHash && genesisHash !== ledgerTemplate.genesisHash)) { - d.error = RequestError.UnsupportedNetwork; - setTimeout(() => { - MessageApi.send(d); - }, 500); - return; - } + }, 500); + return; + } - // We've validated the ledger information - // So we can set the ledger, genesisID, and genesisHash - const ledger = ledgerTemplate.name; - genesisID = ledgerTemplate.genesisId; - genesisHash = ledgerTemplate.genesisHash; - // Then reflect those changes for the page - d.body.params.ledger = ledger; // For legacy name use - d.body.params.genesisID = genesisID; - d.body.params.genesisHash = genesisHash; - - // If we already have the ledger authorized for this origin then check the shared accounts - if (Task.isAuthorized(d.origin)) { - // First check that we actually still have the addresses requested - try { - accounts.forEach(account => { - InternalMethods.checkAccountIsImported(genesisID, account); - }); + // We've validated the ledger information + // So we can set the ledger, genesisID, and genesisHash + const ledger = ledgerTemplate.name; + genesisID = ledgerTemplate.genesisId; + genesisHash = ledgerTemplate.genesisHash; + + // Then reflect those changes for the page + d.body.params.ledger = ledger; + d.body.params.genesisID = genesisID; + d.body.params.genesisHash = genesisHash; - // If the ledger and ALL accounts are available then respond with the cached data - if (Task.isPreAuthorized(d.origin, genesisID, accounts)) { - // We have the accounts and may include additional, but just make sure the order is maintained - const sharedAccounts = []; - accounts.forEach(account => { + // If we already have the ledger authorized for this origin then check the shared accounts + if (Task.isAuthorized(d.origin)) { + // First check that we actually still have the addresses requested + try { + requestedAccounts.forEach((account) => { + InternalMethods.checkAccountIsImported(genesisID, account); + }); + + // If the ledger and ALL accounts are available then respond with the cached data + if (Task.isPreAuthorized(d.origin, genesisID, requestedAccounts)) { + // We have the accounts and may include additional, but just make sure the order is maintained + const sharedAccounts = []; + requestedAccounts.forEach((account) => { + // Make sure we don't include accounts that have been deleted + InternalMethods.checkAccountIsImported(genesisID, account); + sharedAccounts.push(account); + }); + Task.authorized_pool_details[d.origin]['accounts'].forEach((account) => { + if (!sharedAccounts.includes(account)) { // Make sure we don't include accounts that have been deleted InternalMethods.checkAccountIsImported(genesisID, account); sharedAccounts.push(account); - }); - Task.authorized_pool_details[d.origin]['accounts'].forEach(account => { - if (!(sharedAccounts.includes(account))) { - // Make sure we don't include accounts that have been deleted - InternalMethods.checkAccountIsImported(genesisID, account); - sharedAccounts.push(account); - } - }); + } + }); - // Now we can set the response, but don't need to update the cache - d.response = { - 'genesisID': genesisID, - 'genesisHash': genesisHash, - accounts: sharedAccounts - }; - MessageApi.send(d); - return; - } + // Now we can set the response, but don't need to update the cache + d.response = { + genesisID: genesisID, + genesisHash: genesisHash, + accounts: sharedAccounts, + }; + MessageApi.send(d); + return; } - catch { - // Failure means we won't auto authorize, but we can sink the error as we are re-prompting - } + } catch { + // Failure means we won't auto authorize, but we can sink the error as we are re-prompting } + } - // We haven't immediately failed and don't have preAuthorization so we need to prompt accounts. - const promptedAccounts = []; - - // Add any requested accounts so they can be in the proper order to start - if (accounts) { - for (let i = 0; i < accounts.length; i++) { - // We initially push accounts as missing and don't have them selected - // If we also own the address it will be modified to not missing and selected by default - const requestedAddress = accounts[i]; - promptedAccounts.push({ - address: requestedAddress, - missing: true, - requested: true, - }); - } + // We haven't immediately failed and don't have preAuthorization so we need to prompt accounts. + const promptedAccounts = []; + + // Add any requested accounts so they can be in the proper order to start + if (requestedAccounts) { + for (let i = 0; i < requestedAccounts.length; i++) { + // We initially push accounts as missing and don't have them selected + // If we also own the address it will be modified to not missing and selected by default + const requestedAddress = requestedAccounts[i]; + promptedAccounts.push({ + address: requestedAddress, + missing: true, + requested: true, + }); } + } - // Get wallet accounts for the specified ledger - const walletAccounts = session.wallet[ledger]; - - // If we need a requested a ledger but don't have it, respond with an error - if (walletAccounts === undefined) { - d.error = RequestError.UnsupportedNetwork; + // Add the prompted accounts to the params that will go to the page + d.body.params['promptedAccounts'] = promptedAccounts; - setTimeout(() => { - MessageApi.send(d); - }, 500); - return; - } - - // Add all the walletAccounts we have for the ledger - for (let i = 0; i < walletAccounts.length; i++) { - const walletAccount = walletAccounts[i].address; - const accountIndex = promptedAccounts.findIndex(e => e.address === walletAccount); - - if (accountIndex > -1) { - // If we have the account then mark it as valid - promptedAccounts[accountIndex]['missing'] = false; - promptedAccounts[accountIndex]['selected'] = true; - } - else { - // If we are missing the address then this is an account that the dApp did not request - // but we can push the value an the additional choices from the user before returning - promptedAccounts.push({ - address: walletAccount, - requested: false, - selected: false - }); - } - } - - // Add the prompted accounts to the params that will go to the page - d.body.params['promptedAccounts'] = promptedAccounts; - - extensionBrowser.windows.create( - { - url: extensionBrowser.runtime.getURL('index.html#/enable'), - ...popupProperties, - }, - function (w: any) { - if (w) { - Task.requests[d.originTabID] = { - window_id: w.id, - message: d, - }; - setTimeout(function () { - extensionBrowser.runtime.sendMessage(d); - }, 500); - } + extensionBrowser.windows.create( + { + url: extensionBrowser.runtime.getURL('index.html#/enable'), + ...popupProperties, + }, + function (w: any) { + if (w) { + Task.requests[d.originTabID] = { + window_id: w.id, + message: d, + }; + setTimeout(function () { + extensionBrowser.runtime.sendMessage(d); + }, 500); } - ); - } + } + ); }, // sign-wallet-transaction [JsonRpcMethod.SignWalletTransaction]: async ( @@ -1671,41 +1619,43 @@ export class Task { }); } } - } + } // Get an internal session and get wallet accounts for the new chosen ledger const session = InternalMethods.getHelperSession(); - const walletAccounts = session.wallet[ledger]; - + const walletAccounts = session.wallet && session.wallet[ledger]; + // We only need to add accounts if we actually have them if (walletAccounts) { - // Add all the walletAccounts we have for the ledger + // Add all the walletAccounts we have for the ledger for (let i = 0; i < walletAccounts.length; i++) { const walletAccount = walletAccounts[i].address; - const accountIndex = newPromptedAccounts.findIndex(e => e.address === walletAccount); - + const accountIndex = newPromptedAccounts.findIndex( + (e) => e.address === walletAccount + ); + if (accountIndex > -1) { - // If we have the account then mark it as valid + // If we have the account then mark it as valid newPromptedAccounts[accountIndex]['missing'] = false; newPromptedAccounts[accountIndex]['selected'] = true; - } - else { + } else { // If we are missing the address then this is an account that the dApp did not request // but we can push the value an the additional choices from the user before returning newPromptedAccounts.push({ address: walletAccount, requested: false, - selected: false + selected: false, }); } - } + } } - // Replace the prompted accounts on params that will go back to the page - request.body.params['promptedAccounts'] = newPromptedAccounts; - - // Respond with the new params - sendResponse(request.body.params); + // Replace the prompted accounts on params response + const response = { + ...request.body.params, + promptedAccounts: newPromptedAccounts, + }; + sendResponse(response); }, }, }; diff --git a/packages/ui/src/pages/Enable.ts b/packages/ui/src/pages/Enable.ts index 8d765865..a5acd64e 100644 --- a/packages/ui/src/pages/Enable.ts +++ b/packages/ui/src/pages/Enable.ts @@ -29,7 +29,6 @@ const Enable: FunctionalComponent = () => { const [genesisHash, setGenesisHash] = useState(''); const [networkSpecifiedType, setNetworkSpecifiedType] = useState(''); const [accounts, setPromptedAccounts] = useState([]); - const [request, setRequest] = useState({}); const [active, setActive] = useState(false); let sessionLedgers; let ddClass: string = 'dropdown'; @@ -42,16 +41,13 @@ const Enable: FunctionalComponent = () => { availableLedgers.find((l) => l.genesisId === genesisID && l.genesisHash === genesisHash) ); } else if (networkSpecifiedType === 2) { - for (let i = 0; i < availableLedgers.length; i++) { - if (availableLedgers[i]['genesisId'] === genesisID) { - restrictedLedgers.push(availableLedgers[i]); - } - } + restrictedLedgers.push( + availableLedgers.find((l) => l.genesisId === genesisID) + ); } else { restrictedLedgers = availableLedgers; } sessionLedgers = restrictedLedgers; - store.setLedger(restrictedLedgers[0].name); } }); @@ -73,29 +69,28 @@ const Enable: FunctionalComponent = () => { if (params.genesisHash) { setGenesisHash(params.genesisHash); } - if (params.ledger) { - // Ledger is added during EnableAuthorization to match with legacy ledger name and with GetEnableAccounts - store.setLedger(params.ledger); - } if (params.networkSpecifiedType) { setNetworkSpecifiedType(params.networkSpecifiedType); } }; - const setLedger = (ledger) => { - store.setLedger(ledger); - flip(); - - // Set the new ledger to be loaded - request.body.params['ledger'] = ledger; - - sendMessage(JsonRpcMethod.GetEnableAccounts, request.body.params, function (response) { - if (response.error) { - console.error(response.error); - } else { - setDetails(response); + const setLedger = (ledger: string) => { + if (ledger && store.savedRequest?.body) { + store.setLedger(ledger); + + // Set the new ledger to be loaded + const params = { + ...store.savedRequest.body.params, + ledger: ledger, } - }); + sendMessage(JsonRpcMethod.GetEnableAccounts, params, function (response) { + if (response.error) { + console.error(response.error); + } else { + setDetails(response); + } + }); + } }; useEffect(() => { @@ -103,18 +98,19 @@ const Enable: FunctionalComponent = () => { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.body.method == JsonRpcMethod.EnableAuthorization) { // Set the request in the store with origin so we can respond later - setRequest(request); store.saveRequest(request); responseOriginTabID = request.originTabID; - - // Check for existence of the params and set page values - setDetails(request.body.params); + setLedger(request?.body?.params?.ledger); } }); window.addEventListener('beforeunload', deny); return () => window.removeEventListener('beforeunload', deny); }, []); + useEffect(() => { + setLedger(store.savedRequest?.body?.params?.ledger); + }, [store.savedRequest]); + const grant = () => { window.removeEventListener('beforeunload', deny); chrome.runtime.sendMessage({ @@ -143,18 +139,18 @@ const Enable: FunctionalComponent = () => {
- ${request.favIconUrl && - html` `} + ${store.savedRequest?.favIconUrl && + html` `}

Access requested to your - wallet${request.originTitle && html` from ${request.originTitle}`} + wallet${store.savedRequest?.originTitle && html` from ${store.savedRequest?.originTitle}`}

Select the accounts to - share${request.originTitle && html` with ${request.originTitle}`}. + share${store.savedRequest?.originTitle && html` with ${store.savedRequest?.originTitle}`}. Bolded accounts are required by the dApp.

@@ -189,7 +185,7 @@ const Enable: FunctionalComponent = () => { html` setLedger(availableLedger.name)} + onClick=${() => { setLedger(availableLedger.name); flip(); }} class="dropdown-item" > ${availableLedger.name} diff --git a/packages/ui/src/pages/Login.ts b/packages/ui/src/pages/Login.ts index 2adcf491..d6fafc7d 100644 --- a/packages/ui/src/pages/Login.ts +++ b/packages/ui/src/pages/Login.ts @@ -42,13 +42,9 @@ const Login: FunctionalComponent = (props: any) => { store.setAvailableLedgers(response.availableLedgers); store.updateWallet(response.wallet, () => { store.setLedger(response.ledger); - if (redirect.length > 0 && redirect === 'close') { - window.close(); - } - else if (redirect.length > 0) { + if (redirect.length > 0) { route(`/${redirect}`); - } - else { + } else { route('/wallet'); } }); From 71b258288ff7041558bcb54152c4efdbd5faa36e Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 25 Jan 2023 15:14:36 -0300 Subject: [PATCH 07/44] Update jest & common tests --- packages/test-project/package.json | 4 ++-- packages/test-project/tests/common/tests.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/test-project/package.json b/packages/test-project/package.json index 935b7404..2e7e5544 100644 --- a/packages/test-project/package.json +++ b/packages/test-project/package.json @@ -6,10 +6,10 @@ "description": "Repository for tests", "devDependencies": { "algosdk": "2.0.0", - "jest": "^28.1.0", + "jest": "^29.4.0", "jest-runner-groups": "^2.2.0", "puppeteer": "^13.7.0", - "ts-jest": "^28.0.2", + "ts-jest": "^29.0.5", "typescript": "^4.6.4" }, "scripts": { diff --git a/packages/test-project/tests/common/tests.js b/packages/test-project/tests/common/tests.js index d1f300c0..c301b0bb 100644 --- a/packages/test-project/tests/common/tests.js +++ b/packages/test-project/tests/common/tests.js @@ -188,7 +188,7 @@ function ConnectWithAlgorandObject(accounts) { }) ).resolves.toMatchObject({ message: expect.stringContaining('The extension user has not'), - code: 4100, + code: 4202, }); }); @@ -315,7 +315,7 @@ function ConnectWithAlgoSignerObject() { }) ).resolves.toMatchObject({ message: expect.stringContaining('The extension user has not'), - code: 4100, + code: 4202, }); }); From 93d08705e8c71251e8f4ce86c367af14c734bbf9 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 25 Jan 2023 15:58:50 -0300 Subject: [PATCH 08/44] Unify 'genesisId' into 'genesisID' (v1 vestige) --- packages/common/src/types/ledgers.ts | 12 +++++------ packages/extension/src/background/config.ts | 12 +++++------ .../messaging/internalMethods.test.ts | 8 ++++---- .../background/messaging/internalMethods.ts | 10 +++++----- .../src/background/messaging/task.ts | 19 +++++++++--------- .../src/background/transaction/actions.ts | 20 +++++++++---------- .../src/background/utils/validator.ts | 4 ++-- .../LedgerDevice/LedgerHardwareSign.ts | 2 +- .../ui/src/components/LedgerNetworkModify.ts | 10 +++++----- .../components/LedgerNetworksConfiguration.ts | 2 +- packages/ui/src/pages/Enable.ts | 4 ++-- .../ui/src/pages/SignWalletTransaction.ts | 4 ++-- 12 files changed, 53 insertions(+), 54 deletions(-) diff --git a/packages/common/src/types/ledgers.ts b/packages/common/src/types/ledgers.ts index b27939bb..2caaae47 100644 --- a/packages/common/src/types/ledgers.ts +++ b/packages/common/src/types/ledgers.ts @@ -1,7 +1,7 @@ export class LedgerTemplate { name: string; readonly isEditable: boolean; - genesisId?: string; + genesisID?: string; genesisHash?: string; symbol?: string; algodUrl?: string; @@ -14,7 +14,7 @@ export class LedgerTemplate { constructor({ name, - genesisId, + genesisID, genesisHash, symbol, algodUrl, @@ -22,7 +22,7 @@ export class LedgerTemplate { headers, }: { name: string; - genesisId?: string; + genesisID?: string; genesisHash?: string; symbol?: string; algodUrl?: string; @@ -34,7 +34,7 @@ export class LedgerTemplate { } this.name = name; - this.genesisId = genesisId || 'mainnet-v1.0'; + this.genesisID = genesisID || 'mainnet-v1.0'; this.genesisHash = genesisHash; this.symbol = symbol; this.algodUrl = algodUrl; @@ -49,12 +49,12 @@ export function getBaseSupportedLedgers(): Array { return [ new LedgerTemplate({ name: 'MainNet', - genesisId: 'mainnet-v1.0', + genesisID: 'mainnet-v1.0', genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=', }), new LedgerTemplate({ name: 'TestNet', - genesisId: 'testnet-v1.0', + genesisID: 'testnet-v1.0', genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', }), ]; diff --git a/packages/extension/src/background/config.ts b/packages/extension/src/background/config.ts index d8a80659..0660e2f5 100644 --- a/packages/extension/src/background/config.ts +++ b/packages/extension/src/background/config.ts @@ -44,7 +44,7 @@ export class Settings { for (var i = 0; i < injectedNetworkKeys.length; i++) { injectedNetworks.push({ name: this.backend_settings.InjectedNetworks[injectedNetworkKeys[i]].name, - genesisId: this.backend_settings.InjectedNetworks[injectedNetworkKeys[i]].genesisId, + genesisID: this.backend_settings.InjectedNetworks[injectedNetworkKeys[i]].genesisID, }); } @@ -86,9 +86,9 @@ export class Settings { } } - // Add the algod links defaulting the url to one based on the genesisId + // Add the algod links defaulting the url to one based on the genesisID let defaultUrl = 'https://algosigner.api.purestake.io/mainnet'; - if (ledger.genesisId && ledger.genesisId.indexOf('testnet') > -1) { + if (ledger.genesisID && ledger.genesisID.indexOf('testnet') > -1) { defaultUrl = 'https://algosigner.api.purestake.io/testnet'; } @@ -125,10 +125,10 @@ export class Settings { } public static addInjectedNetwork(ledger: LedgerTemplate) { - // Initialize the injected network with the genesisId and a name that mimics the ledger for reference + // Initialize the injected network with the genesisID and a name that mimics the ledger for reference this.backend_settings.InjectedNetworks[ledger.name] = { name: ledger.name, - genesisId: ledger.genesisId || '', + genesisID: ledger.genesisID || '', }; this.setInjectedHeaders(ledger); @@ -149,7 +149,7 @@ export class Settings { this.deleteInjectedNetwork(previousName); this.backend_settings.InjectedNetworks[targetName] = {}; } - this.backend_settings.InjectedNetworks[targetName].genesisId = updatedLedger.genesisId; + this.backend_settings.InjectedNetworks[targetName].genesisID = updatedLedger.genesisID; this.backend_settings.InjectedNetworks[targetName].symbol = updatedLedger.symbol; this.backend_settings.InjectedNetworks[targetName].genesisHash = updatedLedger.genesisHash; diff --git a/packages/extension/src/background/messaging/internalMethods.test.ts b/packages/extension/src/background/messaging/internalMethods.test.ts index 26b8a73a..f0781f98 100644 --- a/packages/extension/src/background/messaging/internalMethods.test.ts +++ b/packages/extension/src/background/messaging/internalMethods.test.ts @@ -98,7 +98,7 @@ describe('wallet flow', () => { { algodUrl: undefined, genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=', - genesisId: 'mainnet-v1.0', + genesisID: 'mainnet-v1.0', headers: undefined, indexerUrl: undefined, isEditable: false, @@ -108,7 +108,7 @@ describe('wallet flow', () => { { algodUrl: undefined, genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - genesisId: 'testnet-v1.0', + genesisID: 'testnet-v1.0', headers: undefined, indexerUrl: undefined, isEditable: false, @@ -142,7 +142,7 @@ describe('wallet flow', () => { { algodUrl: undefined, genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=', - genesisId: 'mainnet-v1.0', + genesisID: 'mainnet-v1.0', headers: undefined, indexerUrl: undefined, isEditable: false, @@ -152,7 +152,7 @@ describe('wallet flow', () => { { algodUrl: undefined, genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', - genesisId: 'testnet-v1.0', + genesisID: 'testnet-v1.0', headers: undefined, indexerUrl: undefined, isEditable: false, diff --git a/packages/extension/src/background/messaging/internalMethods.ts b/packages/extension/src/background/messaging/internalMethods.ts index 212f11b3..82200eff 100644 --- a/packages/extension/src/background/messaging/internalMethods.ts +++ b/packages/extension/src/background/messaging/internalMethods.ts @@ -16,7 +16,7 @@ import { ValidationStatus } from '../utils/validator'; import { calculateEstimatedFee, getValidatedTxnWrap, - getLedgerFromGenesisId, + getLedgerFromGenesisID, getLedgerFromMixedGenesis, } from '../transaction/actions'; import { BaseValidatedTxnWrap } from '../transaction/baseValidatedTxnWrap'; @@ -100,7 +100,7 @@ export class InternalMethods { // Checks if an account for the given address exists on AlgoSigner for a given ledger. public static checkAccountIsImported(genesisID: string, address: string): void { - const ledger: string = getLedgerFromGenesisId(genesisID); + const ledger: string = getLedgerFromGenesisID(genesisID); let found = false; for (let i = session.wallet[ledger].length - 1; i >= 0; i--) { if (session.wallet[ledger][i].address === address) { @@ -500,7 +500,7 @@ export class InternalMethods { sendResponse({ message: message }); } else if (session.txnRequest.source === 'ui') { // If this is an UI transaction then we need to submit to the network - const ledger = getLedgerFromGenesisId(decodedTxn.txn.genesisID); + const ledger = getLedgerFromGenesisID(decodedTxn.txn.genesisID); const algod = this.getAlgod(ledger); algod @@ -1044,7 +1044,7 @@ export class InternalMethods { const targetName = previousName ? previousName : params['name'].toLowerCase(); const addedLedger = new LedgerTemplate({ name: params['name'], - genesisId: params['genesisId'], + genesisID: params['genesisID'], genesisHash: params['genesisHash'], symbol: params['symbol'], algodUrl: params['algodUrl'], @@ -1070,7 +1070,7 @@ export class InternalMethods { if (!defaultLedgers.some((dledg) => dledg.uniqueName === matchingLedger.uniqueName)) { Settings.updateInjectedNetwork(addedLedger, previousName); matchingLedger.name = addedLedger.name; - matchingLedger.genesisId = addedLedger.genesisId; + matchingLedger.genesisID = addedLedger.genesisID; matchingLedger.symbol = addedLedger.symbol; matchingLedger.genesisHash = addedLedger.genesisHash; matchingLedger.algodUrl = addedLedger.algodUrl; diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index 071f04b0..4a53074e 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -7,7 +7,7 @@ import { Ledger } from '@algosigner/common/types'; import { API } from './types'; import { getValidatedTxnWrap, - getLedgerFromGenesisId, + getLedgerFromGenesisID, getLedgerFromMixedGenesis, } from '../transaction/actions'; import { BaseValidatedTxnWrap } from '../transaction/baseValidatedTxnWrap'; @@ -46,7 +46,6 @@ export class Task { } // Validate the genesisID is the authorized one - // Note: The arc-0006 requires "genesisID" which matches the transaction, but we use "genesisId" internally in some places if (!Task.authorized_pool_details[origin] || !(Task.authorized_pool_details[origin]['genesisID'] === genesisID)) { return false; } @@ -104,7 +103,7 @@ export class Task { public static getChainAuthAddress = async (transaction: any): Promise => { // The ledger and address will be provided differently from UI and dapp - const ledger = transaction.ledger || getLedgerFromGenesisId(transaction.genesisID); + const ledger = transaction.ledger || getLedgerFromGenesisID(transaction.genesisID); const address = transaction.address || transaction.from; const conn = Settings.getBackendParams(ledger, API.Algod); @@ -158,7 +157,7 @@ export class Task { if (transactionWrap.transaction['type'] === 'axfer') { const assetIndex = transactionWrap.transaction['assetIndex']; - const ledger = getLedgerFromGenesisId(transactionWrap.transaction['genesisID']); + const ledger = getLedgerFromGenesisID(transactionWrap.transaction['genesisID']); const conn = Settings.getBackendParams(ledger, API.Algod); const sendPath = `/v2/assets/${assetIndex}`; const fetchAssets: any = { @@ -307,7 +306,7 @@ export class Task { if (!algosdk.isValidAddress(authAddr)) { throw RequestError.InvalidAuthAddress(authAddr); } - Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisId, ledger.genesisHash, authAddr, request.origin); + Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisID, ledger.genesisHash, authAddr, request.origin); } // If we have msigData, we validate the addresses and fetch the resulting msig address @@ -372,7 +371,7 @@ export class Task { // We make sure we have the available accounts for signing signers.forEach((address) => { try { - Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisId, ledger.genesisHash, address, request.origin); + Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisID, ledger.genesisHash, address, request.origin); } catch (e) { throw RequestError.CantMatchMsigSigners(e.message); } @@ -401,7 +400,7 @@ export class Task { } else { // There's no signers field, we validate the sender if there's no msig if (!msigData) { - Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisId, ledger.genesisHash, wrap.transaction.from, request.origin); + Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisID, ledger.genesisHash, wrap.transaction.from, request.origin); } } @@ -628,7 +627,7 @@ export class Task { // Validate that the genesis id and hash if provided match the resulting one // This is because a dapp may request an id and hash from different ledgers if ( - (genesisID && genesisID !== ledgerTemplate.genesisId) || + (genesisID && genesisID !== ledgerTemplate.genesisID) || (genesisHash && ledgerTemplate.genesisHash && genesisHash !== ledgerTemplate.genesisHash) @@ -643,7 +642,7 @@ export class Task { // We've validated the ledger information // So we can set the ledger, genesisID, and genesisHash const ledger = ledgerTemplate.name; - genesisID = ledgerTemplate.genesisId; + genesisID = ledgerTemplate.genesisID; genesisHash = ledgerTemplate.genesisHash; // Then reflect those changes for the page @@ -1128,7 +1127,7 @@ export class Task { const signErrors = []; try { - const ledger = getLedgerFromGenesisId(transactionObjs[0].genesisID); + const ledger = getLedgerFromGenesisID(transactionObjs[0].genesisID); const neededAccounts: Array = []; walletTransactions.forEach((w, i) => { const msig = w.msig; diff --git a/packages/extension/src/background/transaction/actions.ts b/packages/extension/src/background/transaction/actions.ts index e02088f2..d360acbe 100644 --- a/packages/extension/src/background/transaction/actions.ts +++ b/packages/extension/src/background/transaction/actions.ts @@ -125,33 +125,33 @@ export function getValidatedTxnWrap( return validatedTxnWrap; } -export function getLedgerFromGenesisId(genesisId: string): string { +export function getLedgerFromGenesisID(genesisID: string): string { // Default the ledger to mainnet const defaultLedger = 'MainNet'; // Check Genesis ID for base supported ledgers first const defaultLedgers = getBaseSupportedLedgers(); - let ledger = defaultLedgers.find((l) => genesisId === l['genesisId']); + let ledger = defaultLedgers.find((l) => genesisID === l['genesisID']); if (ledger !== undefined) { return ledger.name; } // Injected networks may have additional information, multiples, or additional checks // so we will check them separately - ledger = Settings.getCleansedInjectedNetworks().find((l) => genesisId === l['genesisId']); + ledger = Settings.getCleansedInjectedNetworks().find((l) => genesisID === l['genesisID']); if (ledger !== undefined) { return ledger.name; } return defaultLedger; } -export function getLedgerFromMixedGenesis(genesisId: string, genesisHash?: string): LedgerTemplate { +export function getLedgerFromMixedGenesis(genesisID: string, genesisHash?: string): LedgerTemplate { // Check Genesis Id and Hash for base supported ledgers first const defaultLedgers = getBaseSupportedLedgers(); let ledger; - if (genesisId) { - ledger = defaultLedgers.find((l) => genesisId === l['genesisId']); + if (genesisID) { + ledger = defaultLedgers.find((l) => genesisID === l['genesisID']); if (ledger !== undefined) { - // Found genesisId, make sure the hash matches + // Found genesisID, make sure the hash matches if (!genesisHash || genesisHash === ledger.genesisHash) { return ledger; } @@ -159,7 +159,7 @@ export function getLedgerFromMixedGenesis(genesisId: string, genesisHash?: strin // Injected networks may have additional validations so we check them separately const injectedNetworks = Settings.getCleansedInjectedNetworks(); - ledger = injectedNetworks.find((network) => network['genesisId'] === genesisId); + ledger = injectedNetworks.find((network) => network['genesisID'] === genesisID); if (ledger) { return ledger; } @@ -170,7 +170,7 @@ export function getLedgerFromMixedGenesis(genesisId: string, genesisHash?: strin ledger = defaultLedgers.find((l) => genesisHash === l['genesisHash']); if (ledger !== undefined) { // Found genesisHash, make sure the id matches - if (!genesisId || genesisId === ledger.genesisId) { + if (!genesisID || genesisID === ledger.genesisID) { return ledger; } } @@ -203,7 +203,7 @@ export function calculateEstimatedFee(transactionWrap: BaseValidatedTxnWrap, par dummy replica transaction that's similar to what the SDK eventually sends For this we ignore all empty fields and shorten field names to 4 characters so we use smaller field names like the SDK does - i.e.: the SDK uses 'gen' fpr 'genesisId', 'amt' for 'amount', etc + i.e.: the SDK uses 'gen' for 'genesisID', 'amt' for 'amount', etc */ const dummyTransaction = {}; Object.keys(transaction).map((key, index) => { diff --git a/packages/extension/src/background/utils/validator.ts b/packages/extension/src/background/utils/validator.ts index aafb7857..6d49a41b 100644 --- a/packages/extension/src/background/utils/validator.ts +++ b/packages/extension/src/background/utils/validator.ts @@ -217,8 +217,8 @@ export function Validate(field: any, value: any): ValidationResponse { // Genesis ID must be present and one of the approved values case 'genesisID': if ( - getBaseSupportedLedgers().some((l) => value === l['genesisId']) || - Settings.getCleansedInjectedNetworks().find((l) => value === l['genesisId']) + getBaseSupportedLedgers().some((l) => value === l['genesisID']) || + Settings.getCleansedInjectedNetworks().find((l) => value === l['genesisID']) ) { return new ValidationResponse({ status: ValidationStatus.Valid }); } else { diff --git a/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts b/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts index 45f3f4f1..114b6f17 100644 --- a/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts +++ b/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts @@ -64,7 +64,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { setTxn(txToSign); getBaseSupportedLedgers().forEach((l) => { - if (txToSign.transaction?.genesisID === l['genesisId']) { + if (txToSign.transaction?.genesisID === l['genesisID']) { const fetchedLedger = l['name']; setLedger(fetchedLedger); store.setLedger(fetchedLedger); diff --git a/packages/ui/src/components/LedgerNetworkModify.ts b/packages/ui/src/components/LedgerNetworkModify.ts index 88327c9b..49400d3d 100644 --- a/packages/ui/src/components/LedgerNetworkModify.ts +++ b/packages/ui/src/components/LedgerNetworkModify.ts @@ -19,7 +19,7 @@ const LedgerNetworkModify: FunctionalComponent = (props: any) => { const [authError, setAuthError] = useState(''); const [loading, setLoading] = useState(false); const [networkName, setNetworkName] = useState(props.name || ''); - const [networkId, setNetworkId] = useState(props.genesisId || ''); + const [networkID, setNetworkID] = useState(props.genesisID || ''); const [networkSymbol, setNetworkSymbol] = useState(props.symbol || ''); const [networkAlgodUrl, setNetworkAlgodUrl] = useState(props.algodUrl || ''); const [networkIndexerUrl, setNetworkIndexerUrl] = useState(props.indexerUrl || ''); @@ -66,7 +66,7 @@ const LedgerNetworkModify: FunctionalComponent = (props: any) => { setError(''); const params = { name: networkName, - genesisId: networkId, + genesisID: networkID, symbol: networkSymbol, algodUrl: networkAlgodUrl, indexerUrl: networkIndexerUrl, @@ -97,7 +97,7 @@ const LedgerNetworkModify: FunctionalComponent = (props: any) => { const params = { name: networkName, previousName: previousName, - genesisId: networkId, + genesisID: networkID, symbol: networkSymbol, algodUrl: networkAlgodUrl, indexerUrl: networkIndexerUrl, @@ -159,8 +159,8 @@ const LedgerNetworkModify: FunctionalComponent = (props: any) => { id="networkId" class="input" placeholder="mainnet-v1.0" - value=${networkId} - onInput=${(e) => setNetworkId(e.target.value)} + value=${networkID} + onInput=${(e) => setNetworkID(e.target.value)} /> { } }} name=${activeLedgerTemplate.name} - genesisId=${activeLedgerTemplate.genesisId} + genesisID=${activeLedgerTemplate.genesisID} symbol=${activeLedgerTemplate.symbol} algodUrl=${activeLedgerTemplate.algodUrl} indexerUrl=${activeLedgerTemplate.indexerUrl} diff --git a/packages/ui/src/pages/Enable.ts b/packages/ui/src/pages/Enable.ts index a5acd64e..510d6f5b 100644 --- a/packages/ui/src/pages/Enable.ts +++ b/packages/ui/src/pages/Enable.ts @@ -38,11 +38,11 @@ const Enable: FunctionalComponent = () => { let restrictedLedgers: any[] = []; if (networkSpecifiedType === 1) { restrictedLedgers.push( - availableLedgers.find((l) => l.genesisId === genesisID && l.genesisHash === genesisHash) + availableLedgers.find((l) => l.genesisID === genesisID && l.genesisHash === genesisHash) ); } else if (networkSpecifiedType === 2) { restrictedLedgers.push( - availableLedgers.find((l) => l.genesisId === genesisID) + availableLedgers.find((l) => l.genesisID === genesisID) ); } else { restrictedLedgers = availableLedgers; diff --git a/packages/ui/src/pages/SignWalletTransaction.ts b/packages/ui/src/pages/SignWalletTransaction.ts index 10ea56e2..420df37e 100644 --- a/packages/ui/src/pages/SignWalletTransaction.ts +++ b/packages/ui/src/pages/SignWalletTransaction.ts @@ -183,7 +183,7 @@ const SignWalletTransaction: FunctionalComponent = () => { // Search for ledger and find accounts let txLedger; getBaseSupportedLedgers().forEach((l) => { - if (transactionWraps[0].transaction.genesisID === l['genesisId']) { + if (transactionWraps[0].transaction.genesisID === l['genesisID']) { txLedger = l['name']; setLedger(txLedger); findAccountNames(txLedger); @@ -197,7 +197,7 @@ const SignWalletTransaction: FunctionalComponent = () => { if (!availableLedgers.error) { sessionLedgers = availableLedgers; sessionLedgers.forEach((l) => { - if (transactionWraps[0].transaction.genesisID === l['genesisId']) { + if (transactionWraps[0].transaction.genesisID === l['genesisID']) { txLedger = l['name']; } }); From 8ee4df7faf7ed51aa8aef34ec71f31377f98d33c Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 25 Jan 2023 15:59:59 -0300 Subject: [PATCH 09/44] Separate 4200 errors for v2/ARCs & update documentation --- docs/dApp-integration.md | 4 +-- docs/legacy-dApp-integration.md | 8 ++--- packages/common/src/errors.ts | 13 ++++--- .../src/background/messaging/task.ts | 34 +++++++++++-------- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/docs/dApp-integration.md b/docs/dApp-integration.md index 48a55436..e2f7f462 100644 --- a/docs/dApp-integration.md +++ b/docs/dApp-integration.md @@ -582,9 +582,9 @@ Some of the following error codes may be returned when interacting with AlgoSign | 4000 | An unknown error occured. | N/A | | 4001 | The user rejected the signature request. | N/A | | 4100 | The requested operation and/or account has not been authorized by the user. | This is usually due to the connection between the dApp and the wallet becoming stale and the user [needing to reconnect](connection-issues.md). Otherwise, it may signal that you are trying to sign with private keys not found on AlgoSigner. | -| 4200 | The wallet does not support the requested operation. | N/A | +| 4200 | The wallet does not support the requested operation. | Users need to have imported or created an account on AlgoSigner before connecting to dApps, as well as succesfully having configured any custom networks required. | | 4201 | The wallet does not support signing that many transactions at a time. | The max number of transactions per group is 16. For Ledger devices, they currently can't sign more than one transaction at the same time. | -| 4202 | The wallet was not initialized properly beforehand. | Users need to have imported or created an account on AlgoSigner before connecting to dApps. | +| 4202 | The wallet was not initialized properly beforehand. | The extension user has not authorized requests from this website. | | 4300 | The input provided is invalid. | AlgoSigner rejected some of the transactions due to invalid fields. | | 4400 | Some transactions were not sent properly. | Some, but not all of the transactions were able to be posted to the network. The IDs of the succesfully posted transactions as well as information on the failing ones are provided on the error. diff --git a/docs/legacy-dApp-integration.md b/docs/legacy-dApp-integration.md index e9228dad..44377839 100644 --- a/docs/legacy-dApp-integration.md +++ b/docs/legacy-dApp-integration.md @@ -524,8 +524,8 @@ AlgoSigner.send({ - A non-matching ledger name will result in a error: - The provided ledger is not supported (Code: 4200). - An empty request will result with an error: - - Ledger not provided. Please use a base ledger: [TestNet,MainNet] or an available custom one [{"name":"Theta","genesisId":"thetanet-v1.0"}]. -- Transaction requests will require a valid matching "genesisId", even for custom networks. + - Ledger not provided. Please use a base ledger: [TestNet,MainNet] or an available custom one [{"name":"Theta","genesisID":"thetanet-v1.0"}]. +- Transaction requests will require a valid matching "genesisID", even for custom networks. ## Signature Rejection Messages @@ -536,9 +536,9 @@ AlgoSigner may return some of the following error codes when requesting signatur | 4000 | An unknown error occured. | N/A | | 4001 | The user rejected the signature request. | N/A | | 4100 | The requested operation and/or account has not been authorized by the user. | This is usually due to the connection between the dApp and the wallet becoming stale and the user [needing to reconnect](connection-issues.md). Otherwise, it may signal that you are trying to sign with private keys not found on AlgoSigner. | -| 4200 | The wallet does not support the requested operation. | N/A | +| 4200 | The wallet does not support the requested operation. | Users need to have imported or created an account on AlgoSigner before connecting to dApps, as well as succesfully having configured any custom networks required. | | 4201 | The wallet does not support signing that many transactions at a time. | The max number of transactions per group is 16. For Ledger devices, they can't sign more than one transaction at the same time. | -| 4202 | The wallet was not initialized properly beforehand. | Users need to have imported or created an account on AlgoSigner before connecting to dApps | +| 4202 | The wallet was not initialized properly beforehand. | The extension user has not authorized requests from this website. | | 4300 | The input provided is invalid. | AlgoSigner rejected some of the transactions due to invalid fields. | Additional information, if available, would be provided in the `data` field of the error object. diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts index 8a676dd7..960d8005 100644 --- a/packages/common/src/errors.ts +++ b/packages/common/src/errors.ts @@ -13,11 +13,8 @@ export class RequestError { 'The extension user does not authorize the request.', 4001 ); - static EnableRejected = (data: object): RequestError => new RequestError( - 'The extension user does not authorize the request.', - 4001, - data - ); + static EnableRejected = (data: object): RequestError => + new RequestError('The extension user does not authorize the request.', 4001, data); static NoMnemonicAvailable = (address: string): RequestError => new RequestError( `The user does not possess the required private key to sign with for address: "${address}".`, @@ -28,6 +25,12 @@ export class RequestError { `No matching account found on AlgoSigner for address "${address}" on network ${ledger}.`, 4100 ); + static NoLedgerProvided = (base: string, injected: string): RequestError => + new RequestError( + `Ledger not provided. Please use a base ledger: [${base}] or an available custom one ${injected}.`, + 4200 + ); + static UnsupportedLedger = new RequestError('The provided ledger is not supported.', 4200); static UnsupportedNetwork = new RequestError('The provided network is not supported.', 4200); static PendingTransaction = new RequestError('Another query processing', 4201); static LedgerMultipleGroups = new RequestError( diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index 4a53074e..bcf40967 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -435,13 +435,21 @@ export class Task { let data = ''; let code = 4300; - // We format the validation errors for better readability and clarity - validationErrors.forEach((error, index) => { - // Concatenate the errors in a single formatted message - data = data + `Validation failed for transaction ${index} due to: ${error.message}. `; - // Take the lowest error code as they're _more_ generic the higher they go - code = error.code < code ? error.code : code; - }); + if (walletTransactions.length > 1) { + // We format the validation errors for better readability and clarity + validationErrors.forEach((error, index) => { + // Concatenate the errors in a single formatted message + data = data + `Validation failed for transaction ${index} due to: ${error.message} `; + // Take the lowest error code as they're _more_ generic the higher they go + code = error.code < code ? error.code : code; + }); + } else { + const error = validationErrors[0]; + if (error) { + data = data + `Validation failed on transaction due to: ${error.message}`; + code = error.code; + } + } throw RequestError.SigningError(code, data.trim()); } else if ( transactionWraps.some( @@ -1016,14 +1024,10 @@ export class Task { [JsonRpcMethod.Accounts]: (d: any, resolve: Function, reject: Function) => { const session = InternalMethods.getHelperSession(); // If we don't have a ledger requested, respond with an error giving available ledgers - if (!d.body.params.ledger) { - const baseNetworks = Object.keys(Ledger); + if (!d.body.params?.ledger) { + const baseLedgers = Object.keys(Ledger); const injectedNetworks = Settings.getCleansedInjectedNetworks(); - d.error = { - message: `Ledger not provided. Please use a base ledger: [${baseNetworks}] or an available custom one ${JSON.stringify( - injectedNetworks - )}.`, - }; + d.error = RequestError.NoLedgerProvided(baseLedgers.toString(), JSON.stringify(injectedNetworks)); reject(d); return; } @@ -1031,7 +1035,7 @@ export class Task { const accounts = session.wallet[d.body.params.ledger]; // If we have requested a ledger but don't have it, respond with an error if (accounts === undefined) { - d.error = RequestError.UnsupportedNetwork; + d.error = RequestError.UnsupportedLedger; reject(d); return; } From c8c42c7d17a95ff2954de6e0773209a25faed540 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 25 Jan 2023 17:17:45 -0300 Subject: [PATCH 10/44] Update GitHub Actions to node 16 --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/main.yml | 18 +++++++++--------- .github/workflows/zip-release.yml | 6 +++--- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8979a276..a0ac2b34 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,7 +23,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 686be5b8..9a3ad17b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,10 +12,10 @@ jobs: env: CI: 'true' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '12.15' + node-version: '16' - name: Install build dependencies run: npm run install:extension - name: Run unit tests @@ -26,10 +26,10 @@ jobs: env: CI: 'true' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '12.15' + node-version: '16' - name: Install test dependencies env: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true' @@ -53,10 +53,10 @@ jobs: env: CI: 'true' steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '12.15' + node-version: '16' - name: Install build dependencies run: npm run install:extension - name: Build diff --git a/.github/workflows/zip-release.yml b/.github/workflows/zip-release.yml index 88d0bcb4..b0646d0b 100644 --- a/.github/workflows/zip-release.yml +++ b/.github/workflows/zip-release.yml @@ -9,10 +9,10 @@ jobs: test_zip_upload: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2-beta + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 with: - node-version: '12.15' + node-version: '16' - name: Delete all dist run: npm run clean - name: Install dependencies From d633f7b49fab9dfaad63334f69cc292327363480 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 25 Jan 2023 17:27:13 -0300 Subject: [PATCH 11/44] Update 'upload-artifact' to v3 --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9a3ad17b..58bb23b9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,7 @@ jobs: args: npm run test:github - name: Upload screenshots if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: screenshot path: ./packages/test-project/screenshots/*.png @@ -62,7 +62,7 @@ jobs: - name: Build run: npm run build - name: Upload build artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: build path: ./dist/* \ No newline at end of file From 0da3757715e9ad2d02d8e147d12801859b4882ea Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Tue, 31 Jan 2023 11:48:52 -0300 Subject: [PATCH 12/44] Switch Puppeteer action for a node16 compatible --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 58bb23b9..d6d40a8f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '16.15' - name: Install build dependencies run: npm run install:extension - name: Run unit tests @@ -29,7 +29,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '16.15' - name: Install test dependencies env: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: 'true' @@ -37,7 +37,7 @@ jobs: - name: Build run: npm run build - name: Run headful puppeteer tests - uses: djp3/puppeteer-headful@master + uses: mujo-code/puppeteer-headful@16.6.0 with: args: npm run test:github - name: Upload screenshots @@ -56,7 +56,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '16.15' - name: Install build dependencies run: npm run install:extension - name: Build From b809ab773c95ba75164a1e59af592cc3abda64c1 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Tue, 31 Jan 2023 11:50:44 -0300 Subject: [PATCH 13/44] Update Release Drafter defaults --- .github/release-drafter.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 3c778438..dc892e3e 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,3 +1,6 @@ +name-template: "Patch $NEXT_PATCH_VERSION" +tag-template: "v$NEXT_PATCH_VERSION" +commitish: refs/heads/release template: | ## Updates From f8c1a82676e28c9e877d0260796c79bb9ccc8cbf Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Tue, 31 Jan 2023 11:52:03 -0300 Subject: [PATCH 14/44] Switch Build uploader action for a node16 compatible --- .github/workflows/zip-release.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/zip-release.yml b/.github/workflows/zip-release.yml index b0646d0b..a0cd776e 100644 --- a/.github/workflows/zip-release.yml +++ b/.github/workflows/zip-release.yml @@ -6,13 +6,13 @@ on: - released jobs: - test_zip_upload: + build_zip_upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: '16' + node-version: '16.15' - name: Delete all dist run: npm run clean - name: Install dependencies @@ -22,12 +22,9 @@ jobs: - name: Zip up release id: zip_up run: zip -r AlgoSigner.zip ./dist/* - - name: Upload - uses: actions/upload-release-asset@v1.0.1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload build as asset + uses: svenstaro/upload-release-action@v2 with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ./AlgoSigner.zip - asset_name: AlgoSigner.zip - asset_content_type: application/zip \ No newline at end of file + repo_token: ${{ secrets.GITHUB_TOKEN }} + tag: ${{ github.ref }} + file: AlgoSigner.zip \ No newline at end of file From 6b449df9c0fc6f2bd78ef12bf4f263c9a8cf919a Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Tue, 31 Jan 2023 12:12:53 -0300 Subject: [PATCH 15/44] Update Jest version --- packages/test-project/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/test-project/package.json b/packages/test-project/package.json index 935b7404..2c2f5db0 100644 --- a/packages/test-project/package.json +++ b/packages/test-project/package.json @@ -6,10 +6,10 @@ "description": "Repository for tests", "devDependencies": { "algosdk": "2.0.0", - "jest": "^28.1.0", + "jest": "^29.4.1", "jest-runner-groups": "^2.2.0", "puppeteer": "^13.7.0", - "ts-jest": "^28.0.2", + "ts-jest": "^29.0.5", "typescript": "^4.6.4" }, "scripts": { From fd887307e9955b8fface011708032d0bdb6e5782 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:19:37 -0300 Subject: [PATCH 16/44] Update error building and concatenating --- packages/common/src/common.test.ts | 2 +- packages/common/src/errors.ts | 16 ++- .../src/background/messaging/task.ts | 128 ++++++------------ .../background/transaction/actions.test.ts | 86 +++++------- .../src/background/transaction/actions.ts | 4 +- 5 files changed, 93 insertions(+), 143 deletions(-) diff --git a/packages/common/src/common.test.ts b/packages/common/src/common.test.ts index 83ee3526..f1b13978 100644 --- a/packages/common/src/common.test.ts +++ b/packages/common/src/common.test.ts @@ -44,7 +44,7 @@ test('RequestError - Structure', () => { code: 4100, }); - expect(RequestError.SigningError(4000, [testError])).toMatchObject({ + expect(RequestError.SigningValidationError(4000, [testError])).toMatchObject({ message: 'There was a problem signing the transaction(s).', code: 4000, data: [testError], diff --git a/packages/common/src/errors.ts b/packages/common/src/errors.ts index f3050753..f5f5e636 100644 --- a/packages/common/src/errors.ts +++ b/packages/common/src/errors.ts @@ -9,6 +9,8 @@ export class RequestError { static None = new RequestError('', 0); static Undefined = new RequestError('An undefined error occurred.', 4000); + static ApplySignatureError = (data?: any): RequestError => + new RequestError('There was a problem signing the transaction(s).', 4000, data); static UserRejected = new RequestError( 'The extension user does not authorize the request.', 4001 @@ -49,10 +51,10 @@ export class RequestError { 'The extension user has not authorized requests from this website.', 4202 ); - static InvalidFields = (data?: any): RequestError => - new RequestError('Validation failed for transaction due to invalid properties.', 4300, data); - static InvalidTransactionStructure = (data?: any): RequestError => - new RequestError('Validation failed for transaction due to invalid structure.', 4300, data); + static InvalidFields = (fields?: any): RequestError => + new RequestError(`Validation failed for transaction since it has invalid properties: [${fields.join(', ')}]`, 4300); + static InvalidTransactionStructure = (reason?: any): RequestError => + new RequestError(`Validation failed for transaction since it has an invalid structure: ${reason}`, 4300); static InvalidSignTxnsFormat = new RequestError( 'Please provide an array of either valid transaction objects or nested arrays of valid transaction objects.', 4300 @@ -102,7 +104,7 @@ export class RequestError { "There are no transactions to sign as the provided ones are for reference-only ('{ signers: [] }').", 4300 ); - static InvalidStructure = new RequestError( + static InvalidWalletTxnStructure = new RequestError( "The provided transaction object doesn't adhere to the correct structure.", 4300 ); @@ -153,8 +155,8 @@ export class RequestError { 'The transaction(s) were succesfully sent to the network, but there was an issue while waiting for confirmation. Please verify that they were commited to the network before trying again.', 4400 ); - static SigningError = (code: number, data?: any): RequestError => - new RequestError('There was a problem signing the transaction(s).', code, data); + static SigningValidationError = (code: number, data?: any): RequestError => + new RequestError('There was a problem validating the transaction(s) to be signed. Please refer to the data property for the reasons behind each transaction.', code, data); protected constructor(message: string, code: number, data?: any) { this.name = 'AlgoSignerRequestError'; diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index 39b5b1e1..e157b029 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -258,7 +258,7 @@ export class Task { (walletTx.msig && typeof walletTx.msig !== 'object') ) { logging.log('Invalid Wallet Transaction Structure'); - throw RequestError.InvalidStructure; + throw RequestError.InvalidWalletTxnStructure; } else if ( // prettier-ignore walletTx.msig && ( @@ -291,6 +291,22 @@ export class Task { const wrap = getValidatedTxnWrap(processedTx, processedTx['type']); transactionWraps[index] = wrap; + if ( + wrap.validityObject && + Object.values(wrap.validityObject).some( + (value) => value['status'] === ValidationStatus.Invalid + ) + ) { + // We have a transaction that contains fields which are deemed invalid. We should reject the transaction. + const invalidKeys = []; + Object.entries(wrap.validityObject).forEach(([key, value]) => { + if (value['status'] === ValidationStatus.Invalid) { + invalidKeys.push(`${key}: ${value['info']}`); + } + }); + throw RequestError.InvalidFields(invalidKeys); + } + const ledger = getLedgerFromMixedGenesis(wrap.transaction.genesisID, wrap.transaction.genesisHash); const signers: Array = walletTransactions[index].signers; @@ -431,58 +447,19 @@ export class Task { !transactionWraps.length || transactionWraps.some((w) => w === undefined || w === null) ) { - // We don't have transaction wraps or we have an building error, reject the transaction. - let data = ''; + // We don't have transaction wraps or we have a validation error, reject the transaction. + const data = []; let code = 4300; - if (walletTransactions.length > 1) { - // We format the validation errors for better readability and clarity - validationErrors.forEach((error, index) => { - // Concatenate the errors in a single formatted message - data = data + `Validation failed for transaction ${index} due to: ${error.message} `; - // Take the lowest error code as they're _more_ generic the higher they go - code = error.code < code ? error.code : code; - }); - } else { - const error = validationErrors[0]; - if (error) { - data = data + `Validation failed on transaction due to: ${error.message}`; - code = error.code; - } - } - throw RequestError.SigningError(code, data.trim()); - } else if ( - transactionWraps.some( - (tx) => - tx.validityObject && - Object.values(tx.validityObject).some( - (value) => value['status'] === ValidationStatus.Invalid - ) - ) - ) { - // We have a transaction that contains fields which are deemed invalid. We should reject the transaction. - // We can use a modified popup that allows users to review the transaction and invalid fields and close the transaction. - const invalidKeys = {}; - transactionWraps.forEach((tx, index) => { - invalidKeys[index] = []; - Object.entries(tx.validityObject).forEach(([key, value]) => { - if (value['status'] === ValidationStatus.Invalid) { - invalidKeys[index].push(`${key}: ${value['info']}`); - } - }); - if (!invalidKeys[index].length) delete invalidKeys[index]; - }); - - let data = ''; - Object.keys(invalidKeys).forEach((index) => { - data = - data + - `Validation failed for transaction #${index} because of invalid properties [${invalidKeys[ - index - ].join(', ')}]. `; + // We format the validation errors for better readability and clarity + validationErrors.forEach((error, index) => { + // Store the error message in the corresponding array position + data[index] = error.message; + // Take the lowest error code as they're _more_ generic the higher they go + code = error.code < code ? error.code : code; }); - throw RequestError.InvalidFields(data); + throw RequestError.SigningValidationError(code, data); } else { /** * Group validations @@ -555,21 +532,19 @@ export class Task { ); } } catch (e) { - let data = ''; + const errorResponse = { ...e }; - if (groupsToSign.length === 1) { - data += e.data; - } else { - data += `On group ${currentGroup}: [${e.data}]. `; + if (groupsToSign.length > 1) { + errorResponse.message = `On group ${currentGroup}: ${e.message}`; } - logging.log(data); - request.error = e; - request.error.data = data; + logging.log(errorResponse); + request.error = errorResponse // Clean class saved request delete Task.requests[request.originTabID]; MessageApi.send(request); + return; } }; @@ -1135,7 +1110,7 @@ export class Task { let holdResponse = false; const signedTxs = []; - const signErrors = []; + const signErrors: Array = []; try { const ledger = getLedgerFromGenesisID(transactionObjs[0].genesisID); @@ -1318,19 +1293,18 @@ export class Task { // We check if there were errors signing this group if (signErrors.length) { - let data = ''; - if (transactionObjs.length > 1) { - signErrors.forEach((error, index) => { - data += `On transaction ${index}, the error was: ${error}. `; - }); - } else { - data += signErrors[0]; - } + const data = []; + signErrors.forEach((error, index) => { + data[index] = error; + }); + + const responseError = RequestError.ApplySignatureError(data); if (!singleGroup) { - data = `On group ${currentGroup}: [${data.trim()}]. `; + responseError.message = `On group ${currentGroup}: ${responseError.message}`; } - message.error = RequestError.SigningError(4000, data); - logging.log(data); + + message.error = responseError; + logging.log(responseError); } else { signedGroups[currentGroup] = signedTxs; } @@ -1348,21 +1322,7 @@ export class Task { if (message.body.params.currentGroup + 1 < groupsToSign.length) { // More groups to sign, continue prompting user message.body.params.currentGroup = currentGroup + 1; - try { - await Task.signIndividualGroup(message); - } catch (e) { - let errorMessage = 'There was a problem validating the transaction(s). '; - - if (singleGroup) { - errorMessage += e.message; - } else { - errorMessage += `On group ${currentGroup}: [${e.message}].`; - } - logging.log(errorMessage); - const error = new Error(errorMessage); - sendResponse(error); - return; - } + await Task.signIndividualGroup(message); } else { // No more groups to sign, build final user-facing response if (opts && opts[OptsKeys.sendTxns]) { diff --git a/packages/extension/src/background/transaction/actions.test.ts b/packages/extension/src/background/transaction/actions.test.ts index 73c5e9d4..86c73781 100644 --- a/packages/extension/src/background/transaction/actions.test.ts +++ b/packages/extension/src/background/transaction/actions.test.ts @@ -172,20 +172,18 @@ test('Validate pay transaction required fields', () => { type: 'pay', }; let message: string = undefined; - let data: string = undefined; try { getValidatedTxnWrap(preTransaction, 'pay'); } catch (e) { message = e.message; - data = e.data; } expect(message).toContain('Validation failed'); - expect(data).toContain('firstRound'); - expect(data).toContain('lastRound'); - expect(data).toContain('genesisID'); - expect(data).toContain('genesisHash'); - expect(data).toContain('to'); - expect(data).toContain('from'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('to'); + expect(message).toContain('from'); }); test('Validate clawback transaction required fields', () => { @@ -193,21 +191,19 @@ test('Validate clawback transaction required fields', () => { type: 'axfer', }; let message: string = undefined; - let data: string = undefined; try { getValidatedTxnWrap(preTransaction, 'axfer'); } catch (e) { message = e.message; - data = e.data; } expect(message).toContain('Validation failed'); - expect(data).toContain('firstRound'); - expect(data).toContain('lastRound'); - expect(data).toContain('genesisID'); - expect(data).toContain('genesisHash'); - expect(data).toContain('from'); - expect(data).toContain('assetIndex'); - expect(data).toContain('assetRevocationTarget'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('assetIndex'); + expect(message).toContain('assetRevocationTarget'); }); test('Validate accept transaction required fields', () => { @@ -215,20 +211,18 @@ test('Validate accept transaction required fields', () => { type: 'axfer', }; let message: string = undefined; - let data: string = undefined; try { getValidatedTxnWrap(preTransaction, 'axfer'); } catch (e) { message = e.message; - data = e.data; } expect(message).toContain('Validation failed'); - expect(data).toContain('firstRound'); - expect(data).toContain('lastRound'); - expect(data).toContain('genesisID'); - expect(data).toContain('genesisHash'); - expect(data).toContain('from'); - expect(data).toContain('assetIndex'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('assetIndex'); }); test('Validate create transaction required fields', () => { @@ -236,20 +230,18 @@ test('Validate create transaction required fields', () => { type: 'acfg', }; let message: string = undefined; - let data: string = undefined; try { getValidatedTxnWrap(preTransaction, 'acfg'); } catch (e) { message = e.message; - data = e.data; } expect(message).toContain('Validation failed'); - expect(data).toContain('firstRound'); - expect(data).toContain('lastRound'); - expect(data).toContain('genesisID'); - expect(data).toContain('genesisHash'); - expect(data).toContain('from'); - expect(data).toContain('assetTotal'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('assetTotal'); }); test('Validate destroy transaction required fields', () => { @@ -257,20 +249,18 @@ test('Validate destroy transaction required fields', () => { type: 'acfg', }; let message: string = undefined; - let data: string = undefined; try { getValidatedTxnWrap(preTransaction, 'acfg'); } catch (e) { message = e.message; - data = e.data; } expect(message).toContain('Validation failed'); - expect(data).toContain('firstRound'); - expect(data).toContain('lastRound'); - expect(data).toContain('genesisID'); - expect(data).toContain('genesisHash'); - expect(data).toContain('from'); - expect(data).toContain('assetIndex'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('assetIndex'); }); test('Validate modify asset transaction required fields', () => { @@ -278,18 +268,16 @@ test('Validate modify asset transaction required fields', () => { type: 'acfg', }; let message: string = undefined; - let data: string = undefined; try { getValidatedTxnWrap(preTransaction, 'acfg'); } catch (e) { message = e.message; - data = e.data; } expect(message).toContain('Validation failed'); - expect(data).toContain('firstRound'); - expect(data).toContain('lastRound'); - expect(data).toContain('genesisID'); - expect(data).toContain('genesisHash'); - expect(data).toContain('from'); - expect(data).toContain('assetIndex'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('assetIndex'); }); diff --git a/packages/extension/src/background/transaction/actions.ts b/packages/extension/src/background/transaction/actions.ts index d360acbe..f977f2a7 100644 --- a/packages/extension/src/background/transaction/actions.ts +++ b/packages/extension/src/background/transaction/actions.ts @@ -57,7 +57,7 @@ export function getValidatedTxnWrap( try { validatedTxnWrap = new AssetCreateTransaction(txn as IAssetCreateTx); } catch (e) { - e.data = [error.data, e.data].join(' '); + e.message = [error.message, e.message].join(' '); error = e; } } @@ -65,7 +65,7 @@ export function getValidatedTxnWrap( try { validatedTxnWrap = new AssetDestroyTransaction(txn as IAssetDestroyTx); } catch (e) { - e.data = [error.data, e.data].join(' '); + e.message = [error.message, e.message].join(' '); error = e; } } From 950f50a98df7b903e4c8f346838220454d0df5be Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:20:23 -0300 Subject: [PATCH 17/44] Update Release Drafter template --- .github/release-drafter.yml | 1 - .github/workflows/cla.yml | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index dc892e3e..45f9f2f2 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,6 +1,5 @@ name-template: "Patch $NEXT_PATCH_VERSION" tag-template: "v$NEXT_PATCH_VERSION" -commitish: refs/heads/release template: | ## Updates diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 25cc8e2a..b7296181 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -6,13 +6,13 @@ on: types: [opened,closed,synchronize] jobs: - CLAssistant: + CLA_assistant: runs-on: ubuntu-latest steps: - name: "CLA Assistant" if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' # Beta Release - uses: cla-assistant/github-action@v2.1.3-beta + uses: cla-assistant/github-action@v2.2.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # the below token should have repo scope and must be manually added by you in the repository's secret @@ -24,7 +24,7 @@ jobs: branch: 'main' allowlist: janmarcano,fxgamundi,purestaketdb,PureBrent,mmaurello,ekenigs - #below are the optional inputs - If the optional inputs are not given, then default values will be taken + #below are the optional inputs - If the optional inputs are not given, then default values will be taken remote-organization-name: purestake remote-repository-name: algosigner-cla #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' From e42d466f606fb200c87550b0208d659ddce01b17 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:21:22 -0300 Subject: [PATCH 18/44] Fixed RequiredParamsSet validations --- .../background/transaction/baseValidatedTxnWrap.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/extension/src/background/transaction/baseValidatedTxnWrap.ts b/packages/extension/src/background/transaction/baseValidatedTxnWrap.ts index 902a7448..136c7cc4 100644 --- a/packages/extension/src/background/transaction/baseValidatedTxnWrap.ts +++ b/packages/extension/src/background/transaction/baseValidatedTxnWrap.ts @@ -45,10 +45,13 @@ export class BaseValidatedTxnWrap { foundRequiredParams.push(key); } }); - const missingRequiredParams = requiredParamsSet.filter( - (p) => !foundRequiredParams.includes(p) - ); - missingFields.concat(missingRequiredParams); + if (!foundRequiredParams.length) { + throw RequestError.InvalidTransactionStructure( + `Creation of ${ + txnType.name + } requires at least one of these properties: ${missingFields.toString()}.` + ); + } } // Throwing error here so that missing fields can be combined. From c28b98a6441cb390594e7d231a89c547b56f234f Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:23:08 -0300 Subject: [PATCH 19/44] Updated validations for KeyregTxns and separated them into offline/online --- packages/common/src/interfaces/keyreg.ts | 8 +-- .../background/transaction/actions.test.ts | 71 ++++++++++++++++++- .../src/background/transaction/actions.ts | 22 +++++- ...saction.ts => keyregOfflineTransaction.ts} | 15 ++-- .../transaction/keyregOnlineTransaction.ts | 44 ++++++++++++ .../components/SignTransaction/TxKeyreg.ts | 28 +++++--- 6 files changed, 158 insertions(+), 30 deletions(-) rename packages/extension/src/background/transaction/{keyregTransaction.ts => keyregOfflineTransaction.ts} (66%) create mode 100644 packages/extension/src/background/transaction/keyregOnlineTransaction.ts diff --git a/packages/common/src/interfaces/keyreg.ts b/packages/common/src/interfaces/keyreg.ts index 2049ec57..9002208a 100644 --- a/packages/common/src/interfaces/keyreg.ts +++ b/packages/common/src/interfaces/keyreg.ts @@ -7,11 +7,11 @@ import { IBaseTx } from './baseTx'; // prettier-ignore export interface IKeyRegistrationTx extends IBaseTx { type: string, // "keyreg" - voteKey: string, // ed25519PublicKey "votekey" The root participation public key. - selectionKey: string, // VrfPubkey "selkey" The VRF public key. + voteKey?: string, // ed25519PublicKey "votekey" The root participation public key. + selectionKey?: string, // VrfPubkey "selkey" The VRF public key. stateProofKey?: string, // MerkleSignature "sprfkey" The 64 byte state proof public key commitment. - voteFirst: number, // uint64 "votefst" The first round that the participation key is valid. Not to be confused with the FirstValid round of the keyreg transaction. - voteLast: number, // uint64 "votelst" The last round that the participation key is valid. Not to be confused with the LastValid round of the keyreg transaction. + voteFirst?: number, // uint64 "votefst" The first round that the participation key is valid. Not to be confused with the FirstValid round of the keyreg transaction. + voteLast?: number, // uint64 "votelst" The last round that the participation key is valid. Not to be confused with the LastValid round of the keyreg transaction. voteKeyDilution?: number, // uint64 "votekd" This is the dilution for the 2-level participation key. nonParticipation?: boolean, // bool "nonpart" All new Algorand accounts are participating by default. } diff --git a/packages/extension/src/background/transaction/actions.test.ts b/packages/extension/src/background/transaction/actions.test.ts index 86c73781..1d9f169f 100644 --- a/packages/extension/src/background/transaction/actions.test.ts +++ b/packages/extension/src/background/transaction/actions.test.ts @@ -4,8 +4,9 @@ import { AssetConfigTransaction } from './acfgTransaction'; import { AssetTransferTransaction } from './axferTransaction'; import { AssetCloseTransaction } from './axferCloseTransaction'; import { AssetFreezeTransaction } from './afrzTransaction'; -import { KeyregTransaction } from './keyregTransaction'; import { ApplicationTransaction } from './applTransaction'; +import { OnlineKeyregTransaction } from './keyregOnlineTransaction'; +import { OfflineKeyregTransaction } from './keyregOfflineTransaction'; test('Validate build of pay transaction', () => { const preTransaction = { @@ -57,7 +58,7 @@ test('Validate build of appl transaction', () => { expect(result instanceof ApplicationTransaction).toBe(true); }); -test('Validate build of keygreg transaction', () => { +test('Validate build of keyreg transaction', () => { const preTransaction = { type: 'keyreg', fee: 1000, @@ -67,6 +68,25 @@ test('Validate build of keygreg transaction', () => { stateProofKey: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQNM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', voteFirst: 1, voteLast: 1001, + voteKeyDilution: 1001, + firstRound: 1, + lastRound: 1001, + genesisID: 'testnet-v1.0', + genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + note: new Uint8Array(0), + }; + + const result = getValidatedTxnWrap(preTransaction, 'keyreg'); + expect(result instanceof BaseValidatedTxnWrap).toBe(true); + expect(result instanceof OnlineKeyregTransaction).toBe(true); +}); + +test('Validate build of offline keyreg transaction', () => { + const preTransaction = { + type: 'keyreg', + fee: 1000, + from: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', + stateProofKey: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQNM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', firstRound: 1, lastRound: 1001, genesisID: 'testnet-v1.0', @@ -77,7 +97,7 @@ test('Validate build of keygreg transaction', () => { const result = getValidatedTxnWrap(preTransaction, 'keyreg'); expect(result instanceof BaseValidatedTxnWrap).toBe(true); - expect(result instanceof KeyregTransaction).toBe(true); + expect(result instanceof OfflineKeyregTransaction).toBe(true); }); test('Validate build of acfg transaction', () => { @@ -186,6 +206,51 @@ test('Validate pay transaction required fields', () => { expect(message).toContain('from'); }); +test('Validate keyreg transaction required fields', () => { + const preTransaction = { + type: 'keyreg', + }; + + let message: string = undefined; + try { + getValidatedTxnWrap(preTransaction, 'keyreg'); + } catch (e) { + message = e.message; + } + expect(message).toContain('Validation failed'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('voteKey'); + expect(message).toContain('selectionKey'); + expect(message).toContain('voteFirst'); + expect(message).toContain('voteLast'); + expect(message).toContain('voteKeyDilution'); +}); + +test('Validate offline keyreg transaction required fields', () => { + const preTransaction = { + type: 'keyreg', + nonParticipation: true, + }; + + let message: string = undefined; + try { + getValidatedTxnWrap(preTransaction, 'keyreg'); + } catch (e) { + message = e.message; + } + expect(message).toContain('Validation failed'); + expect(message).toContain('firstRound'); + expect(message).toContain('lastRound'); + expect(message).toContain('genesisID'); + expect(message).toContain('genesisHash'); + expect(message).toContain('from'); + expect(message).toContain('nonParticipation'); +}); + test('Validate clawback transaction required fields', () => { const preTransaction = { type: 'axfer', diff --git a/packages/extension/src/background/transaction/actions.ts b/packages/extension/src/background/transaction/actions.ts index f977f2a7..38cf9335 100644 --- a/packages/extension/src/background/transaction/actions.ts +++ b/packages/extension/src/background/transaction/actions.ts @@ -18,7 +18,8 @@ import { AssetTransferTransaction } from './axferTransaction'; import { AssetAcceptTransaction } from './axferAcceptTransaction'; import { AssetCloseTransaction } from './axferCloseTransaction'; import { AssetClawbackTransaction } from './axferClawbackTransaction'; -import { KeyregTransaction } from './keyregTransaction'; +import { OnlineKeyregTransaction } from './keyregOnlineTransaction'; +import { OfflineKeyregTransaction } from './keyregOfflineTransaction'; import { ApplicationTransaction } from './applTransaction'; import { TransactionType } from '@algosigner/common/types/transaction'; import { RequestError } from '@algosigner/common/errors'; @@ -113,7 +114,24 @@ export function getValidatedTxnWrap( } break; case TransactionType.Keyreg: - validatedTxnWrap = new KeyregTransaction(txn as IKeyRegistrationTx); + // Validate any of the 2 variants of transactions that can occur with keyreg + // Use the second error as the passback error. + try { + validatedTxnWrap = new OnlineKeyregTransaction(txn as IKeyRegistrationTx); + } catch (e) { + error = e; + } + if (!validatedTxnWrap) { + try { + validatedTxnWrap = new OfflineKeyregTransaction(txn as IKeyRegistrationTx); + } catch (e) { + e.message = [e.message, error.message].join(' '); + error = e; + } + } + if (!validatedTxnWrap && error) { + throw error; + } break; case TransactionType.Appl: validatedTxnWrap = new ApplicationTransaction(txn as IApplTx); diff --git a/packages/extension/src/background/transaction/keyregTransaction.ts b/packages/extension/src/background/transaction/keyregOfflineTransaction.ts similarity index 66% rename from packages/extension/src/background/transaction/keyregTransaction.ts rename to packages/extension/src/background/transaction/keyregOfflineTransaction.ts index b6ffb5e6..373c01ee 100644 --- a/packages/extension/src/background/transaction/keyregTransaction.ts +++ b/packages/extension/src/background/transaction/keyregOfflineTransaction.ts @@ -4,15 +4,10 @@ import { BaseValidatedTxnWrap } from './baseValidatedTxnWrap'; /// // Base implementation of the transactions type interface, for use in the export wrapper class below. /// -class KeyRegistrationTx implements IKeyRegistrationTx { +class OfflineKeyRegistrationTx implements IKeyRegistrationTx { type: string = undefined; - voteKey: string = undefined; - selectionKey: string = undefined; stateProofKey?: string = null; - voteFirst: number = undefined; - voteLast: number = undefined; - voteKeyDilution?: number = null; - nonParticipation?: boolean = false; + nonParticipation: boolean = undefined; from: string = undefined; fee?: number = 0; firstRound: number = undefined; @@ -31,9 +26,9 @@ class KeyRegistrationTx implements IKeyRegistrationTx { /// // Mapping, validation and error checking for keyreg transactions prior to sign. /// -export class KeyregTransaction extends BaseValidatedTxnWrap { - txDerivedTypeText: string = 'Key Registration'; +export class OfflineKeyregTransaction extends BaseValidatedTxnWrap { + txDerivedTypeText: string = 'Offline Key Registration'; constructor(params: IKeyRegistrationTx) { - super(params, KeyRegistrationTx); + super(params, OfflineKeyRegistrationTx); } } diff --git a/packages/extension/src/background/transaction/keyregOnlineTransaction.ts b/packages/extension/src/background/transaction/keyregOnlineTransaction.ts new file mode 100644 index 00000000..8907a246 --- /dev/null +++ b/packages/extension/src/background/transaction/keyregOnlineTransaction.ts @@ -0,0 +1,44 @@ +import { RequestError } from '@algosigner/common/errors'; +import { IKeyRegistrationTx } from '@algosigner/common/interfaces/keyreg'; +import { BaseValidatedTxnWrap } from './baseValidatedTxnWrap'; + +/// +// Base implementation of the transactions type interface, for use in the export wrapper class below. +/// +class OnlineKeyRegistrationTx implements IKeyRegistrationTx { + type: string = undefined; + voteKey: string = undefined; + selectionKey: string = undefined; + stateProofKey?: string = null; + voteFirst: number = undefined; + voteLast: number = undefined; + voteKeyDilution: number = undefined; + nonParticipation?: boolean = false; + from: string = undefined; + fee?: number = 0; + firstRound: number = undefined; + lastRound: number = undefined; + note?: string = null; + genesisID: string = undefined; + genesisHash: any = undefined; + group?: string = null; + lease?: any = null; + reKeyTo?: any = null; + flatFee?: any = null; + name?: string = null; + tag?: string = null; +} + +/// +// Mapping, validation and error checking for keyreg transactions prior to sign. +/// +export class OnlineKeyregTransaction extends BaseValidatedTxnWrap { + txDerivedTypeText: string = 'Online Key Registration'; + constructor(params: IKeyRegistrationTx) { + // Additional checks for Key Regs + if (params.nonParticipation) { + throw RequestError.InvalidTransactionStructure(`On creation of OfflineKeyRegistrationTx if nonParticipation is set to true; voteKey, selectionKey, voteFirst, voteLast, and voteKeyDilution must be undefined.`); + } + super(params, OnlineKeyRegistrationTx); + } +} diff --git a/packages/ui/src/components/SignTransaction/TxKeyreg.ts b/packages/ui/src/components/SignTransaction/TxKeyreg.ts index 61a35d02..7ea8c1f7 100644 --- a/packages/ui/src/components/SignTransaction/TxKeyreg.ts +++ b/packages/ui/src/components/SignTransaction/TxKeyreg.ts @@ -17,14 +17,20 @@ const TxKeyreg: FunctionalComponent = (props: any) => {

${tx.group}

`} -
-

Vote Key:

-

${tx.voteKey}

-
-
-

Selection Key:

-

${tx.selectionKey}

-
+ ${tx.voteKey && + html` +
+

Vote Key:

+

${tx.voteKey}

+
+ `} + ${tx.selectionKey && + html` +
+

Selection Key:

+

${tx.selectionKey}

+
+ `} ${tx.stateProofKey && html`
@@ -35,12 +41,12 @@ const TxKeyreg: FunctionalComponent = (props: any) => { ${tx.nonParticipation && html`
-

Non-participation:

-

${tx.nonParticipation}

+

Non Participation:

+

${tx.nonParticipation.toString()}

`}
-

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

+

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

${fee / 1e6} Algos

From 13b0e4a8975521b773c64dc7b2c8a5229a5b0c69 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:29:36 -0300 Subject: [PATCH 20/44] Updated validations for AssetConfigTxns --- .../background/transaction/acfgTransaction.ts | 34 +++---------------- .../background/transaction/actions.test.ts | 9 +++++ .../src/components/SignTransaction/TxAcfg.ts | 29 ++++++++++++++++ 3 files changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/extension/src/background/transaction/acfgTransaction.ts b/packages/extension/src/background/transaction/acfgTransaction.ts index 90c293cd..5c9fa2d7 100644 --- a/packages/extension/src/background/transaction/acfgTransaction.ts +++ b/packages/extension/src/background/transaction/acfgTransaction.ts @@ -14,48 +14,24 @@ class AssetConfigTx implements IAssetConfigTx { note?: string = null; genesisID: string = undefined; genesisHash: any = undefined; + assetManager: string = undefined; + assetReserve: string = undefined; + assetFreeze: string = undefined; + assetClawback: string = undefined; group?: string = null; lease?: any = null; reKeyTo?: any = null; flatFee?: any = null; name?: string = null; tag?: string = null; - - // Modifications must include one of these - assetTotal?: BigInt = null; - assetDecimals?: number = null; - assetDefaultFrozen?: boolean = null; - assetUnitName?: string = null; - assetName?: string = null; - assetURL?: string = null; - assetMetadataHash?: any = null; - assetManager?: string = null; - assetReserve?: string = null; - assetFreeze?: string = null; - assetClawback?: string = null; } -// A set of params in which at least one must have a value -const requiredAcfgParams: Array = [ - 'assetTotal', - 'assetDecimals', - 'assetDefaultFrozen', - 'assetUnitName', - 'assetName', - 'assetURL', - 'assetMetadataHash', - 'assetManager', - 'assetReserve', - 'assetFreeze', - 'assetClawback', -]; - /// // Mapping, validation and error checking for asset config transactions prior to sign. /// export class AssetConfigTransaction extends BaseValidatedTxnWrap { txDerivedTypeText: string = 'Modify Asset'; constructor(params: IAssetConfigTx) { - super(params, AssetConfigTx, requiredAcfgParams); + super(params, AssetConfigTx); } } diff --git a/packages/extension/src/background/transaction/actions.test.ts b/packages/extension/src/background/transaction/actions.test.ts index 1d9f169f..b996d190 100644 --- a/packages/extension/src/background/transaction/actions.test.ts +++ b/packages/extension/src/background/transaction/actions.test.ts @@ -110,6 +110,10 @@ test('Validate build of acfg transaction', () => { lastRound: 1001, genesisID: 'testnet-v1.0', genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', + assetManager: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', + assetReserve: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', + assetFreeze: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', + assetClawback: 'NM2MBC673SL7TQIKUXD4JOBR3XQITDCHIMIEODQBUGFMAN54QV2VUYWZNQ', note: new Uint8Array(0), }; @@ -339,10 +343,15 @@ test('Validate modify asset transaction required fields', () => { message = e.message; } expect(message).toContain('Validation failed'); + expect(message).toContain('AssetConfigTx'); expect(message).toContain('firstRound'); expect(message).toContain('lastRound'); expect(message).toContain('genesisID'); expect(message).toContain('genesisHash'); expect(message).toContain('from'); expect(message).toContain('assetIndex'); + expect(message).toContain('assetManager'); + expect(message).toContain('assetReserve'); + expect(message).toContain('assetFreeze'); + expect(message).toContain('assetClawback'); }); diff --git a/packages/ui/src/components/SignTransaction/TxAcfg.ts b/packages/ui/src/components/SignTransaction/TxAcfg.ts index 2707eb6e..97377ff0 100644 --- a/packages/ui/src/components/SignTransaction/TxAcfg.ts +++ b/packages/ui/src/components/SignTransaction/TxAcfg.ts @@ -1,3 +1,4 @@ +import { obfuscateAddress } from '@algosigner/common/utils'; import { html } from 'htm/preact'; import { FunctionalComponent } from 'preact'; import TxTemplate from './Common/TxTemplate'; @@ -54,6 +55,34 @@ const TxAcfg: FunctionalComponent = (props: any) => {

${tx.assetTotal}

`} + ${tx.assetManager && + html` +
+

Manager Address:

+

${obfuscateAddress(tx.assetManager)}

+
+ `} + ${tx.assetReserve && + html` +
+

Manager Address:

+

${obfuscateAddress(tx.assetReserve)}

+
+ `} + ${tx.assetFreeze && + html` +
+

Manager Address:

+

${obfuscateAddress(tx.assetFreeze)}

+
+ `} + ${tx.assetClawback && + html` +
+

Manager Address:

+

${obfuscateAddress(tx.assetClawback)}

+
+ `}

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

${fee / 1e6} Algos

From 97b4bfa868fb325d2defd67ce8ce9a6476759a34 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:30:16 -0300 Subject: [PATCH 21/44] Updated validations for AssetCreateTxns --- .../src/background/transaction/acfgCreateTransaction.ts | 4 ++-- packages/extension/src/background/transaction/actions.test.ts | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/extension/src/background/transaction/acfgCreateTransaction.ts b/packages/extension/src/background/transaction/acfgCreateTransaction.ts index a9b99560..c6278c7a 100644 --- a/packages/extension/src/background/transaction/acfgCreateTransaction.ts +++ b/packages/extension/src/background/transaction/acfgCreateTransaction.ts @@ -6,7 +6,8 @@ import { BaseValidatedTxnWrap } from './baseValidatedTxnWrap'; /// class AssetCreateTx implements IAssetCreateTx { assetTotal: BigInt = undefined; - assetDecimals?: number = null; + assetDecimals: number = undefined; + assetDefaultFrozen: boolean = undefined; type: string = undefined; from: string = undefined; fee?: number = 0; @@ -15,7 +16,6 @@ class AssetCreateTx implements IAssetCreateTx { note?: string = null; genesisID: string = undefined; genesisHash: any = undefined; - assetDefaultFrozen?: boolean = null; assetUnitName?: string = null; assetName?: string = null; assetURL?: string = null; diff --git a/packages/extension/src/background/transaction/actions.test.ts b/packages/extension/src/background/transaction/actions.test.ts index b996d190..39a71777 100644 --- a/packages/extension/src/background/transaction/actions.test.ts +++ b/packages/extension/src/background/transaction/actions.test.ts @@ -305,12 +305,15 @@ test('Validate create transaction required fields', () => { message = e.message; } expect(message).toContain('Validation failed'); + expect(message).toContain('AssetCreateTx'); expect(message).toContain('firstRound'); expect(message).toContain('lastRound'); expect(message).toContain('genesisID'); expect(message).toContain('genesisHash'); expect(message).toContain('from'); expect(message).toContain('assetTotal'); + expect(message).toContain('assetDecimals'); + expect(message).toContain('assetDefaultFrozen'); }); test('Validate destroy transaction required fields', () => { @@ -324,6 +327,7 @@ test('Validate destroy transaction required fields', () => { message = e.message; } expect(message).toContain('Validation failed'); + expect(message).toContain('AssetDestroyTx'); expect(message).toContain('firstRound'); expect(message).toContain('lastRound'); expect(message).toContain('genesisID'); From f33ced4fc757b65e632f752b45164a2c4d2f9e73 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Mon, 6 Feb 2023 13:33:08 -0300 Subject: [PATCH 22/44] Remove unused validation styling classes --- .../extension/src/background/utils/validator.ts | 15 --------------- .../ui/src/components/SignTransaction/TxAcfg.ts | 2 +- .../ui/src/components/SignTransaction/TxAfrz.ts | 2 +- .../ui/src/components/SignTransaction/TxAppl.ts | 13 +++++-------- .../ui/src/components/SignTransaction/TxAxfer.ts | 2 +- .../ui/src/components/SignTransaction/TxPay.ts | 2 +- 6 files changed, 9 insertions(+), 27 deletions(-) diff --git a/packages/extension/src/background/utils/validator.ts b/packages/extension/src/background/utils/validator.ts index 6d49a41b..3263cec5 100644 --- a/packages/extension/src/background/utils/validator.ts +++ b/packages/extension/src/background/utils/validator.ts @@ -16,19 +16,6 @@ export enum ValidationStatus { Warning = 2, // Field is out of normal parameters and should be inspected closely Dangerous = 3, // Field has risky or costly fields with values and should be inspected very closely } -/// -// Helper to convert a validation status into a classname for display purposes -/// -function _convertFieldResponseToClassname(validationStatus: ValidationStatus): string { - switch (validationStatus) { - case ValidationStatus.Dangerous: - return 'tx-field-danger'; - case ValidationStatus.Warning: - return 'tx-field-warning'; - default: - return ''; - } -} /// // Validation responses. @@ -36,11 +23,9 @@ function _convertFieldResponseToClassname(validationStatus: ValidationStatus): s export class ValidationResponse { status: ValidationStatus; info: string; - className: string; constructor(props) { this.status = props.status; this.info = props.info; - this.className = _convertFieldResponseToClassname(this.status); } } diff --git a/packages/ui/src/components/SignTransaction/TxAcfg.ts b/packages/ui/src/components/SignTransaction/TxAcfg.ts index 97377ff0..315e6711 100644 --- a/packages/ui/src/components/SignTransaction/TxAcfg.ts +++ b/packages/ui/src/components/SignTransaction/TxAcfg.ts @@ -83,7 +83,7 @@ const TxAcfg: FunctionalComponent = (props: any) => {

${obfuscateAddress(tx.assetClawback)}

`} -
+

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

${fee / 1e6} Algos

diff --git a/packages/ui/src/components/SignTransaction/TxAfrz.ts b/packages/ui/src/components/SignTransaction/TxAfrz.ts index 1f06ca41..81a3da0d 100644 --- a/packages/ui/src/components/SignTransaction/TxAfrz.ts +++ b/packages/ui/src/components/SignTransaction/TxAfrz.ts @@ -53,7 +53,7 @@ const TxAfrz: FunctionalComponent = (props: any) => {

Asset:

${assetIndex}
-
+

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

${fee / 1e6} Algos

diff --git a/packages/ui/src/components/SignTransaction/TxAppl.ts b/packages/ui/src/components/SignTransaction/TxAppl.ts index 1bc72bce..15dd58d1 100644 --- a/packages/ui/src/components/SignTransaction/TxAppl.ts +++ b/packages/ui/src/components/SignTransaction/TxAppl.ts @@ -143,19 +143,16 @@ const TxAppl: FunctionalComponent = (props: any) => {

Boxes: (ID | Name)

${tx.boxes.map((item) => { - return html`${'\u2022'} ${item.appIndex} | ${item.name}`; + return html` + ${'\u2022'} ${item.appIndex} | ${item.name} + `; })}

`}
-

- ${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'} -

-

${fee / 1e6} Algos

+

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

+

${fee / 1e6} Algos

`; diff --git a/packages/ui/src/components/SignTransaction/TxAxfer.ts b/packages/ui/src/components/SignTransaction/TxAxfer.ts index 16310078..b7ddc93c 100644 --- a/packages/ui/src/components/SignTransaction/TxAxfer.ts +++ b/packages/ui/src/components/SignTransaction/TxAxfer.ts @@ -57,7 +57,7 @@ const TxAxfer: FunctionalComponent = (props: any) => {

Asset:

${assetIndex} -
+

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

${fee / 1e6} Algos

diff --git a/packages/ui/src/components/SignTransaction/TxPay.ts b/packages/ui/src/components/SignTransaction/TxPay.ts index 955bff08..4db6be7f 100644 --- a/packages/ui/src/components/SignTransaction/TxPay.ts +++ b/packages/ui/src/components/SignTransaction/TxPay.ts @@ -42,7 +42,7 @@ const TxPay: FunctionalComponent = (props: any) => {

Sending:

${tx.amount / 1e6} Algos

-
+

${!estFee || tx['flatFee'] ? 'Fee:' : 'Estimated fee:'}

${fee / 1e6} Algos

From d1e6577ec393fc73f5c218c8eb762b90ae8f953d Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Thu, 9 Feb 2023 11:07:24 -0300 Subject: [PATCH 23/44] Misc UI fixes and updates --- .../ui/src/components/Account/AssetsList.ts | 2 +- .../components/CreateAccount/AccountKeys.ts | 38 +++++++++++++------ packages/ui/src/pages/ImportAccount.ts | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/packages/ui/src/components/Account/AssetsList.ts b/packages/ui/src/components/Account/AssetsList.ts index 5321cba4..3bba0bc6 100644 --- a/packages/ui/src/components/Account/AssetsList.ts +++ b/packages/ui/src/components/Account/AssetsList.ts @@ -120,7 +120,7 @@ const AssetsList: FunctionalComponent = (props: any) => {
diff --git a/packages/ui/src/components/CreateAccount/AccountKeys.ts b/packages/ui/src/components/CreateAccount/AccountKeys.ts index eafd965c..7de7c0d2 100644 --- a/packages/ui/src/components/CreateAccount/AccountKeys.ts +++ b/packages/ui/src/components/CreateAccount/AccountKeys.ts @@ -52,17 +52,25 @@ const AccountKeys: FunctionalComponent = (props: any) => {
Public Account address - <${ToClipboard} class="is-pulled-right" style="font-size: 0.9em;" data=${account.address} /> + <${ToClipboard} + class="is-pulled-right" + style="font-size: 0.9em;" + data=${account.address} + />
-

${account.address}

+

+ ${account.address} +

- Mnemonic - <${ToClipboard} class="is-pulled-right" style="font-size: 0.9em;" data=${account.mnemonic} /> + Mnemonic + <${ToClipboard} + class="is-pulled-right" + style="font-size: 0.9em; z-index: 5; position: relative;" + data=${account.mnemonic} + />
- ${grid.map(column => html` -
${column}
- `)} + ${grid.map((column) => html`
${column}
`)}

Make sure you have the entire 25-word mnemonic, or you will lose access to this account forever.

@@ -70,17 +78,23 @@ const AccountKeys: FunctionalComponent = (props: any) => {
-
diff --git a/packages/ui/src/pages/ImportAccount.ts b/packages/ui/src/pages/ImportAccount.ts index b553dfec..827218c7 100644 --- a/packages/ui/src/pages/ImportAccount.ts +++ b/packages/ui/src/pages/ImportAccount.ts @@ -35,7 +35,7 @@ const ImportAccount: FunctionalComponent = (props: any) => { const importAccount = (pwd: string) => { const params = { passphrase: pwd, - mnemonic: mnemonicArray.map((w) => w.trim()).join(' '), + mnemonic: mnemonicArray.filter(Boolean).map((w) => w.trim()).join(' '), address: address, isRef: isRef, name: name.trim(), From f91813e7ad97ea127bd9545ffd2625cef69b5b86 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Thu, 9 Feb 2023 11:08:11 -0300 Subject: [PATCH 24/44] Update validations for AssetCreateTxs --- .../src/background/transaction/acfgCreateTransaction.ts | 4 ++-- packages/ui/src/components/SignTransaction/TxAcfg.ts | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/extension/src/background/transaction/acfgCreateTransaction.ts b/packages/extension/src/background/transaction/acfgCreateTransaction.ts index c6278c7a..b4fa3184 100644 --- a/packages/extension/src/background/transaction/acfgCreateTransaction.ts +++ b/packages/extension/src/background/transaction/acfgCreateTransaction.ts @@ -6,8 +6,8 @@ import { BaseValidatedTxnWrap } from './baseValidatedTxnWrap'; /// class AssetCreateTx implements IAssetCreateTx { assetTotal: BigInt = undefined; - assetDecimals: number = undefined; - assetDefaultFrozen: boolean = undefined; + assetDecimals: number = 0; + assetDefaultFrozen: boolean = false; type: string = undefined; from: string = undefined; fee?: number = 0; diff --git a/packages/ui/src/components/SignTransaction/TxAcfg.ts b/packages/ui/src/components/SignTransaction/TxAcfg.ts index 315e6711..4ffafd43 100644 --- a/packages/ui/src/components/SignTransaction/TxAcfg.ts +++ b/packages/ui/src/components/SignTransaction/TxAcfg.ts @@ -55,6 +55,13 @@ const TxAcfg: FunctionalComponent = (props: any) => {

${tx.assetTotal}

`} + ${Number.isInteger(tx.assetDecimals) && + html` +
+

Asset Decimals:

+

${tx.assetDecimals}

+
+ `} ${tx.assetManager && html`
From 19626249846542bd8dd682a5f4c9a84d35ce1173 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Thu, 9 Feb 2023 11:10:46 -0300 Subject: [PATCH 25/44] Update E2E account and Asset tests --- .../test-project/tests/common/constants.js | 4 ++-- .../tests/ui-transactions-e2e.test.js | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/test-project/tests/common/constants.js b/packages/test-project/tests/common/constants.js index 6b84b291..b0996a99 100644 --- a/packages/test-project/tests/common/constants.js +++ b/packages/test-project/tests/common/constants.js @@ -10,8 +10,8 @@ module.exports = { ui: { name: 'E2E-Tests', mnemonic: - 'grape topple reform pistol excite salute loud spike during draw drink planet naive high treat captain dutch cloth more bachelor attend attract magnet ability heavy', - address: 'MTHFSNXBMBD4U46Z2HAYAOLGD2EV6GQBPXVTL727RR3G44AJ3WVFMZGSBE', + 'belt middle party tired craft blanket guide want perfect taste couch march goddess render orient chair avocado intact other stand route attend number abandon icon', + address: 'SGQOSQKZQWABMLHY6CPLKFZ4FHURBLVQTMZZQFP4U7LGZHPW3U4MIPXEDU', }, multisig: { name: 'Joined Multisig', diff --git a/packages/test-project/tests/ui-transactions-e2e.test.js b/packages/test-project/tests/ui-transactions-e2e.test.js index c38d3355..b2456315 100644 --- a/packages/test-project/tests/ui-transactions-e2e.test.js +++ b/packages/test-project/tests/ui-transactions-e2e.test.js @@ -32,13 +32,13 @@ describe('Wallet Setup', () => { describe('UI Transactions Tests', () => { const amount = Math.ceil(Math.random() * 3); // txn size, modify multiplier for bulk + const testAssetIndex = 157722252; + const testAssetDecimals = 6; let txId; // returned tx id from send txn let txTitle; // for tx verification - beforeEach(async () => { - jest.setTimeout(15000); - }); + jest.setTimeout(17000); afterAll(async () => { extensionPage.close(); @@ -141,19 +141,20 @@ describe('UI Transactions Tests', () => { await extensionPage.waitForTimeout(100); await extensionPage.click('#selectAsset'); await extensionPage.waitForTimeout(100); - await extensionPage.waitForSelector('#asset-13169404'); - await extensionPage.click('#asset-13169404'); + const assetSelector = `#asset-${testAssetIndex}`; + await extensionPage.waitForSelector(assetSelector); + await extensionPage.click(assetSelector); await extensionPage.waitForTimeout(500); // Test correct decimal handling - await extensionPage.type('#transferAmount', `0.000000000${amount}`); - const actualAmount = await extensionPage.$eval('#transferAmount', (e) => { + await extensionPage.type('#transferAmount', `${(amount / (10 ** (testAssetDecimals + 3))).toFixed(testAssetDecimals + 3)}`); + const enteredAmount = await extensionPage.$eval('#transferAmount', (e) => { const inputValue = e.value; e.value = ''; return inputValue; }); - expect(actualAmount).toMatch('0.000000'); + expect(enteredAmount).toMatch(`0.${new Array(testAssetDecimals).fill(0).join('')}`); // Test actual transfer - await extensionPage.type('#transferAmount', `0.00000${amount}`); + await extensionPage.type('#transferAmount', `${(amount / (10 ** testAssetDecimals).toFixed(testAssetDecimals))}`); // Select an Imported Account from aliases await extensionPage.type('#destinationAddress', 't'); await extensionPage.waitForSelector('.alias-selector-container'); From b597c2e20e2ad1faec40541cc65497eccbae0c61 Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Thu, 9 Feb 2023 11:11:56 -0300 Subject: [PATCH 26/44] Update e2e to new error structures --- packages/common/src/common.test.ts | 2 +- .../background/transaction/actions.test.ts | 2 - .../tests/dapp-arcs-signtxns.test.js | 164 +++++++++--------- .../tests/dapp-legacy-signtxn.test.js | 164 +++++++++--------- 4 files changed, 173 insertions(+), 159 deletions(-) diff --git a/packages/common/src/common.test.ts b/packages/common/src/common.test.ts index f1b13978..a869d2c9 100644 --- a/packages/common/src/common.test.ts +++ b/packages/common/src/common.test.ts @@ -45,7 +45,7 @@ test('RequestError - Structure', () => { }); expect(RequestError.SigningValidationError(4000, [testError])).toMatchObject({ - message: 'There was a problem signing the transaction(s).', + message: 'There was a problem validating the transaction(s) to be signed. Please refer to the data property for the reasons behind each transaction.', code: 4000, data: [testError], }); diff --git a/packages/extension/src/background/transaction/actions.test.ts b/packages/extension/src/background/transaction/actions.test.ts index 39a71777..fb796e36 100644 --- a/packages/extension/src/background/transaction/actions.test.ts +++ b/packages/extension/src/background/transaction/actions.test.ts @@ -312,8 +312,6 @@ test('Validate create transaction required fields', () => { expect(message).toContain('genesisHash'); expect(message).toContain('from'); expect(message).toContain('assetTotal'); - expect(message).toContain('assetDecimals'); - expect(message).toContain('assetDefaultFrozen'); }); test('Validate destroy transaction required fields', () => { diff --git a/packages/test-project/tests/dapp-arcs-signtxns.test.js b/packages/test-project/tests/dapp-arcs-signtxns.test.js index b5c21afd..c6ff3340 100644 --- a/packages/test-project/tests/dapp-arcs-signtxns.test.js +++ b/packages/test-project/tests/dapp-arcs-signtxns.test.js @@ -103,22 +103,24 @@ describe('Txn Signing Validation errors', () => { ); unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.signTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.signTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4100, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(invalidAccount), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(invalidAccount); + expect(signResponse.data[0]).toContain('No matching account found'); }); // Signers validations @@ -167,22 +169,23 @@ describe('Txn Signing Validation errors', () => { txn.signers = [account2.address]; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.signTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.signTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining("When a single-address 'signers'"), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain("a single-address 'signers' is provided"); }); test('Error on Single signer not matching authAddr', async () => { @@ -200,22 +203,23 @@ describe('Txn Signing Validation errors', () => { txn.authAddr = account2.address; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.signTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.signTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining("When a single-address 'signers'"), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain("a single-address 'signers' is provided"); }); test('Error on Invalid signer address', async () => { @@ -233,22 +237,23 @@ describe('Txn Signing Validation errors', () => { txn.signers = [fakeAccount]; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.signTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.signTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(`Signers array contains the invalid address "${fakeAccount}"`), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(`Signers array contains the invalid address "${fakeAccount}"`); }); // AuthAddr validations @@ -267,22 +272,23 @@ describe('Txn Signing Validation errors', () => { txn.authAddr = fakeAccount; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.signTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.signTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(`'authAddr' contains the invalid address "${fakeAccount}"`), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(`'authAddr' contains the invalid address "${fakeAccount}"`); }); // Msig validations @@ -292,22 +298,24 @@ describe('Txn Signing Validation errors', () => { txn.signers = [account1.address, invalidAccount]; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(algorand.signTxns(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(algorand.signTxns(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(invalidAccount), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(invalidAccount); + expect(signResponse.data[0]).toContain('does not currently possess one of'); }); // // @TODO: Wallet Transaction Structure check tests diff --git a/packages/test-project/tests/dapp-legacy-signtxn.test.js b/packages/test-project/tests/dapp-legacy-signtxn.test.js index fec76c61..209e9103 100644 --- a/packages/test-project/tests/dapp-legacy-signtxn.test.js +++ b/packages/test-project/tests/dapp-legacy-signtxn.test.js @@ -103,22 +103,24 @@ describe('Txn Signing Validation errors', () => { ); unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(AlgoSigner.signTxn(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(AlgoSigner.signTxn(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4100, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(invalidAccount), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(invalidAccount); + expect(signResponse.data[0]).toContain('No matching account found'); }); // Signers validations @@ -167,22 +169,23 @@ describe('Txn Signing Validation errors', () => { txn.signers = [account2.address]; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(AlgoSigner.signTxn(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(AlgoSigner.signTxn(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining("When a single-address 'signers'"), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain("a single-address 'signers' is provided"); }); test('Error on Single signer not matching authAddr', async () => { @@ -200,22 +203,23 @@ describe('Txn Signing Validation errors', () => { txn.authAddr = account2.address; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(AlgoSigner.signTxn(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(AlgoSigner.signTxn(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining("When a single-address 'signers'"), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain("a single-address 'signers' is provided"); }); test('Error on Invalid signer address', async () => { @@ -233,22 +237,23 @@ describe('Txn Signing Validation errors', () => { txn.signers = [fakeAccount]; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(AlgoSigner.signTxn(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(AlgoSigner.signTxn(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(`Signers array contains the invalid address "${fakeAccount}"`), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(`Signers array contains the invalid address "${fakeAccount}"`); }); // AuthAddr validations @@ -267,22 +272,23 @@ describe('Txn Signing Validation errors', () => { txn.authAddr = fakeAccount; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(AlgoSigner.signTxn(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(AlgoSigner.signTxn(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(`'authAddr' contains the invalid address "${fakeAccount}"`), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(`'authAddr' contains the invalid address "${fakeAccount}"`); }); // Msig validations @@ -292,22 +298,24 @@ describe('Txn Signing Validation errors', () => { txn.signers = [account1.address, invalidAccount]; unsignedTransactions = [txn]; - await expect( - dappPage.evaluate((transactions) => { - return Promise.resolve(AlgoSigner.signTxn(transactions)) - .then((data) => { - return data; - }) - .catch((error) => { - return error; - }); - }, unsignedTransactions) - ).resolves.toMatchObject({ - message: expect.stringContaining('There was a problem signing the transaction(s).'), + const signResponse = await dappPage.evaluate((transactions) => { + return Promise.resolve(AlgoSigner.signTxn(transactions)) + .then((data) => { + return data; + }) + .catch((error) => { + return error; + }); + }, unsignedTransactions); + expect(signResponse).toMatchObject({ + message: expect.stringContaining('There was a problem validating the transaction(s) to be signed.'), code: 4300, name: expect.stringContaining('AlgoSignerRequestError'), - data: expect.stringContaining(invalidAccount), + data: expect.anything(), }); + expect(signResponse.data).toHaveLength(1); + expect(signResponse.data[0]).toContain(invalidAccount); + expect(signResponse.data[0]).toContain('does not currently possess one of'); }); // // @TODO: Wallet Transaction Structure check tests From 0dc52bd38f8a6a3212ad742cbd2675a05ed650bd Mon Sep 17 00:00:00 2001 From: Jan Marcano Date: Wed, 22 Feb 2023 15:35:22 -0300 Subject: [PATCH 27/44] Decrease ambiguity on 'Ledger' referencing both networks and devices for variable and method names --- packages/common/src/config.ts | 24 +- packages/common/src/messaging/types.ts | 5 +- packages/common/src/types.ts | 8 +- .../src/types/{ledgers.ts => network.ts} | 10 +- packages/common/src/utils.ts | 10 +- packages/extension/src/background/config.ts | 87 ++--- .../messaging/internalMethods.test.ts | 18 +- .../background/messaging/internalMethods.ts | 328 +++++++++--------- .../src/background/messaging/task.ts | 134 ++++--- .../src/background/messaging/types.ts | 45 ++- .../src/background/transaction/actions.ts | 64 ++-- .../background/utils/assetsDetailsHelper.ts | 14 +- .../extension/src/background/utils/helper.ts | 56 +-- .../src/background/utils/validator.ts | 6 +- packages/storage/src/extensionStorage.ts | 8 +- .../LedgerDevice/LedgerHardwareSign.ts | 42 +-- .../components/LedgerNetworksConfiguration.ts | 4 +- packages/ui/src/components/MainHeader.ts | 4 +- .../{LedgerSelect.ts => NetworkSelect.ts} | 32 +- .../src/components/SignTransaction/TxAfrz.ts | 8 +- .../src/components/SignTransaction/TxAxfer.ts | 8 +- packages/ui/src/pages/Enable.ts | 67 ++-- packages/ui/src/pages/Login.ts | 10 +- .../ui/src/pages/SignWalletTransaction.ts | 72 ++-- packages/ui/src/pages/Wallet.ts | 18 +- packages/ui/src/services/StoreContext.ts | 2 +- 26 files changed, 566 insertions(+), 518 deletions(-) rename packages/common/src/types/{ledgers.ts => network.ts} (84%) rename packages/ui/src/components/{LedgerSelect.ts => NetworkSelect.ts} (71%) diff --git a/packages/common/src/config.ts b/packages/common/src/config.ts index 5dbdcce6..050e9c12 100644 --- a/packages/common/src/config.ts +++ b/packages/common/src/config.ts @@ -1,11 +1,11 @@ -import { Namespace, Ledger, Alias } from './types'; +import { Namespace, Network, Alias } from './types'; const MAX_ALIASES_PER_NAMESPACE = 6; // prettier-ignore interface ConfigTemplate { name: string; // Formatted name, used for titles - ledgers: any; // Object holding supported Ledgers as keys, templated API URL as value + networks: any; // Object holding supported networks as keys, templated API URL as value findAliasedAddresses: Function; // How to process the API response to get the aliased addresses array apiTimeout: number; // Amount in ms to wait for the API to respond } @@ -17,25 +17,25 @@ const noop = (): void => { export class AliasConfig { static [Namespace.AlgoSigner_Contacts]: ConfigTemplate = { name: 'AlgoSigner Contact', - ledgers: null, + networks: null, findAliasedAddresses: noop, apiTimeout: 0, }; static [Namespace.AlgoSigner_Accounts]: ConfigTemplate = { name: 'AlgoSigner Account', - ledgers: null, + networks: null, findAliasedAddresses: noop, apiTimeout: 0, }; static [Namespace.NFD]: ConfigTemplate = { name: 'NFDomains', - ledgers: { - [Ledger.TestNet]: + networks: { + [Network.TestNet]: 'https://api.testnet.nf.domains/nfd?prefix=${term}&requireAddresses=true' + `&limit=${MAX_ALIASES_PER_NAMESPACE}`, - [Ledger.MainNet]: + [Network.MainNet]: 'https://api.nf.domains/nfd?prefix=${term}&requireAddresses=true' + `&limit=${MAX_ALIASES_PER_NAMESPACE}`, }, @@ -50,11 +50,11 @@ export class AliasConfig { static [Namespace.ANS]: ConfigTemplate = { name: 'Algorand Name Service', - ledgers: { - [Ledger.TestNet]: + networks: { + [Network.TestNet]: 'https://testnet.api.algonameservice.com/names?pattern=${term}' + `&limit=${MAX_ALIASES_PER_NAMESPACE}`, - [Ledger.MainNet]: + [Network.MainNet]: 'https://api.algonameservice.com/names?pattern=${term}' + `&limit=${MAX_ALIASES_PER_NAMESPACE}`, }, @@ -69,12 +69,12 @@ export class AliasConfig { apiTimeout: 2000, }; - public static getMatchingNamespaces(ledger: string): Array { + public static getMatchingNamespaces(network: string): Array { const matchingNamespaces: Array = []; for (const n in Namespace) { if ( AliasConfig[n] && - (AliasConfig[n].ledgers === null || Object.keys(AliasConfig[n].ledgers).includes(ledger)) + (AliasConfig[n].networks === null || Object.keys(AliasConfig[n].networks).includes(network)) ) { matchingNamespaces.push(n as Namespace); } diff --git a/packages/common/src/messaging/types.ts b/packages/common/src/messaging/types.ts index 201c6cf4..248116e6 100644 --- a/packages/common/src/messaging/types.ts +++ b/packages/common/src/messaging/types.ts @@ -34,11 +34,11 @@ export enum JsonRpcMethod { AssetsAPIList = 'assets-api-list', AssetsVerifiedList = 'assets-verified-list', SignSendTransaction = 'sign-send-transaction', - ChangeLedger = 'change-ledger', + ChangeNetwork = 'change-network', SaveNetwork = 'save-network', CheckNetwork = 'check-network', DeleteNetwork = 'delete-network', - GetLedgers = 'get-ledgers', + GetNetworks = 'get-networks', GetContacts = 'get-contacts', SaveContact = 'save-contact', DeleteContact = 'delete-contact', @@ -73,6 +73,7 @@ export enum MessageSource { Router = 'router', UI = 'ui', } + export type MessageBody = { readonly source: MessageSource; readonly body: JsonRpcBody; diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index b0736341..391fc7fa 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -12,11 +12,17 @@ export type Transaction = { readonly to: TAccount; }; -export enum Ledger { +export enum Network { TestNet = 'TestNet', MainNet = 'MainNet', } +export enum NetworkSelectionType { + NoneProvided, + OnlyIDProvided, + BothProvided, +} + export type WalletMultisigMetadata = { readonly version: number; readonly threshold: number; diff --git a/packages/common/src/types/ledgers.ts b/packages/common/src/types/network.ts similarity index 84% rename from packages/common/src/types/ledgers.ts rename to packages/common/src/types/network.ts index 2caaae47..3eb20277 100644 --- a/packages/common/src/types/ledgers.ts +++ b/packages/common/src/types/network.ts @@ -1,4 +1,4 @@ -export class LedgerTemplate { +export class NetworkTemplate { name: string; readonly isEditable: boolean; genesisID?: string; @@ -44,15 +44,15 @@ export class LedgerTemplate { } } -export function getBaseSupportedLedgers(): Array { - // Need to add access to additional ledger types from import +export function getBaseSupportedNetworks(): Array { + // Need to add access to additional network types from import return [ - new LedgerTemplate({ + new NetworkTemplate({ name: 'MainNet', genesisID: 'mainnet-v1.0', genesisHash: 'wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=', }), - new LedgerTemplate({ + new NetworkTemplate({ name: 'TestNet', genesisID: 'testnet-v1.0', genesisHash: 'SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=', diff --git a/packages/common/src/utils.ts b/packages/common/src/utils.ts index 00090d29..5f467af3 100644 --- a/packages/common/src/utils.ts +++ b/packages/common/src/utils.ts @@ -1,5 +1,5 @@ import { extensionBrowser } from './chrome'; -import { getBaseSupportedLedgers } from './types/ledgers'; +import { getBaseSupportedNetworks } from './types/network'; export function isFromExtension(origin: string): boolean { const s = origin.split('://'); @@ -21,12 +21,12 @@ export function removeEmptyFields(obj: { [index: string]: any }): any { } /** - * Check if a ledger belongs to our base supported ledgers (e.g: links to GoalSeeker) - * @param ledger + * Check if a network belongs to our base supported network (e.g: links to GoalSeeker) + * @param network * @returns boolean */ -export function isLedgerBaseSupported(ledger: string): boolean { - return getBaseSupportedLedgers().map((l) => l.name.toLowerCase()).includes(ledger.toLowerCase()); +export function isBaseSupportedNetwork(network: string): boolean { + return getBaseSupportedNetworks().map((l) => l.name.toLowerCase()).includes(network.toLowerCase()); } /** diff --git a/packages/extension/src/background/config.ts b/packages/extension/src/background/config.ts index 0660e2f5..a7e864d4 100644 --- a/packages/extension/src/background/config.ts +++ b/packages/extension/src/background/config.ts @@ -1,5 +1,5 @@ -import { LedgerTemplate } from '@algosigner/common/types/ledgers'; -import { Ledger } from '@algosigner/common/types'; +import { NetworkTemplate } from '@algosigner/common/types/network'; +import { Network } from '@algosigner/common/types'; import { logging, LogLevel } from '@algosigner/common/logging'; import { Backend, API } from './messaging/types'; import { parseUrlServerAndPort } from './utils/networkUrlParser'; @@ -8,7 +8,7 @@ export class Settings { static backend: Backend = Backend.PureStake; static backend_settings: { [key: string]: any } = { [Backend.PureStake]: { - [Ledger.TestNet]: { + [Network.TestNet]: { [API.Algod]: { url: 'https://algosigner.api.purestake.io/testnet/algod', port: '', @@ -18,7 +18,7 @@ export class Settings { port: '', }, }, - [Ledger.MainNet]: { + [Network.MainNet]: { [API.Algod]: { url: 'https://algosigner.api.purestake.io/mainnet/algod', port: '', @@ -33,8 +33,8 @@ export class Settings { InjectedNetworks: {}, }; - public static deleteInjectedNetwork(ledgerUniqueName: string) { - delete this.backend_settings.InjectedNetworks[ledgerUniqueName]; + public static deleteInjectedNetwork(networkUniqueName: string) { + delete this.backend_settings.InjectedNetworks[networkUniqueName]; } // Returns a copy of Injected networks with just basic information for dApp or display. @@ -45,15 +45,16 @@ export class Settings { injectedNetworks.push({ name: this.backend_settings.InjectedNetworks[injectedNetworkKeys[i]].name, genesisID: this.backend_settings.InjectedNetworks[injectedNetworkKeys[i]].genesisID, + genesisHash: this.backend_settings.InjectedNetworks[injectedNetworkKeys[i]].genesisID, }); } return injectedNetworks; } - private static setInjectedHeaders(ledger: LedgerTemplate, isCheckOnly?: boolean) { - if (!this.backend_settings.InjectedNetworks[ledger.name] && !isCheckOnly) { - console.log('Error: Ledger headers can not be updated. Ledger not available.'); + private static setInjectedHeaders(network: NetworkTemplate, isCheckOnly?: boolean) { + if (!this.backend_settings.InjectedNetworks[network.name] && !isCheckOnly) { + console.log('Error: Network headers can not be updated. Network not available.'); return; } @@ -61,13 +62,13 @@ export class Settings { let headers = {}; let headersAlgod = undefined; let headersIndexer = undefined; - if (ledger['headers']) { + if (network['headers']) { // Set the headers to the base level first, this allows a string key to be used - headers = ledger['headers']; + headers = network['headers']; // Then try to parse the headers, in the case it is a string object. try { - headers = JSON.parse(ledger['headers']); + headers = JSON.parse(network['headers']); } catch (e) { // Use headers default value, but use it as a token if it is a string if (typeof headers === 'string') { @@ -88,13 +89,13 @@ export class Settings { // Add the algod links defaulting the url to one based on the genesisID let defaultUrl = 'https://algosigner.api.purestake.io/mainnet'; - if (ledger.genesisID && ledger.genesisID.indexOf('testnet') > -1) { + if (network.genesisID && network.genesisID.indexOf('testnet') > -1) { defaultUrl = 'https://algosigner.api.purestake.io/testnet'; } // Setup port splits for algod and indexer - used in sandbox installs - const parsedAlgodUrlObj = parseUrlServerAndPort(ledger.algodUrl); - const parsedIndexerUrlObj = parseUrlServerAndPort(ledger.indexerUrl); + const parsedAlgodUrlObj = parseUrlServerAndPort(network.algodUrl); + const parsedIndexerUrlObj = parseUrlServerAndPort(network.indexerUrl); // Add algod links const injectedAlgod = { @@ -118,23 +119,23 @@ export class Settings { indexer: injectedIndexer, }; } else { - this.backend_settings.InjectedNetworks[ledger.name][API.Algod] = injectedAlgod; - this.backend_settings.InjectedNetworks[ledger.name][API.Indexer] = injectedIndexer; - this.backend_settings.InjectedNetworks[ledger.name].headers = headers; + this.backend_settings.InjectedNetworks[network.name][API.Algod] = injectedAlgod; + this.backend_settings.InjectedNetworks[network.name][API.Indexer] = injectedIndexer; + this.backend_settings.InjectedNetworks[network.name].headers = headers; } } - public static addInjectedNetwork(ledger: LedgerTemplate) { - // Initialize the injected network with the genesisID and a name that mimics the ledger for reference - this.backend_settings.InjectedNetworks[ledger.name] = { - name: ledger.name, - genesisID: ledger.genesisID || '', + public static addInjectedNetwork(network: NetworkTemplate) { + // Initialize the injected network with the genesisID and a name that mimics the network for reference + this.backend_settings.InjectedNetworks[network.name] = { + name: network.name, + genesisID: network.genesisID || '', }; - this.setInjectedHeaders(ledger); + this.setInjectedHeaders(network); logging.log( `Added Network:\n${JSON.stringify( - this.backend_settings.InjectedNetworks[ledger.name], + this.backend_settings.InjectedNetworks[network.name], null, 1 )}`, @@ -142,21 +143,21 @@ export class Settings { ); } - public static updateInjectedNetwork(updatedLedger: LedgerTemplate, previousName: string = '') { - const targetName = updatedLedger.uniqueName; + public static updateInjectedNetwork(updatedNetwork: NetworkTemplate, previousName: string = '') { + const targetName = updatedNetwork.uniqueName; if (previousName) { this.deleteInjectedNetwork(previousName); this.backend_settings.InjectedNetworks[targetName] = {}; } - this.backend_settings.InjectedNetworks[targetName].genesisID = updatedLedger.genesisID; - this.backend_settings.InjectedNetworks[targetName].symbol = updatedLedger.symbol; + this.backend_settings.InjectedNetworks[targetName].genesisID = updatedNetwork.genesisID; + this.backend_settings.InjectedNetworks[targetName].symbol = updatedNetwork.symbol; this.backend_settings.InjectedNetworks[targetName].genesisHash = - updatedLedger.genesisHash; - this.backend_settings.InjectedNetworks[targetName].algodUrl = updatedLedger.algodUrl; + updatedNetwork.genesisHash; + this.backend_settings.InjectedNetworks[targetName].algodUrl = updatedNetwork.algodUrl; this.backend_settings.InjectedNetworks[targetName].indexerUrl = - updatedLedger.indexerUrl; - this.setInjectedHeaders(updatedLedger); + updatedNetwork.indexerUrl; + this.setInjectedHeaders(updatedNetwork); logging.log( `Updated Network:\n${JSON.stringify( @@ -168,12 +169,12 @@ export class Settings { ); } - public static getBackendParams(ledger: string, api: API) { + public static getBackendParams(network: string, api: API) { // If we are using the PureStake backend we can return the url, port, and apiKey - if (this.backend_settings[this.backend][ledger]) { + if (this.backend_settings[this.backend][network]) { return { - url: this.backend_settings[this.backend][ledger][api].url, - port: this.backend_settings[this.backend][ledger][api].port, + url: this.backend_settings[this.backend][network][api].url, + port: this.backend_settings[this.backend][network][api].port, apiKey: this.backend_settings[this.backend].apiKey, headers: {}, }; @@ -181,15 +182,15 @@ export class Settings { // Here we have to grab data from injected networks instead of the backend return { - url: this.backend_settings.InjectedNetworks[ledger][api].url, - port: this.backend_settings.InjectedNetworks[ledger][api].port, - apiKey: this.backend_settings.InjectedNetworks[ledger][api].apiKey, - headers: this.backend_settings.InjectedNetworks[ledger][api].headers, + url: this.backend_settings.InjectedNetworks[network][api].url, + port: this.backend_settings.InjectedNetworks[network][api].port, + apiKey: this.backend_settings.InjectedNetworks[network][api].apiKey, + headers: this.backend_settings.InjectedNetworks[network][api].headers, }; } - public static checkNetwork(ledger: LedgerTemplate) { - const networks = this.setInjectedHeaders(ledger, true); + public static checkNetwork(network: NetworkTemplate) { + const networks = this.setInjectedHeaders(network, true); return networks; } } diff --git a/packages/extension/src/background/messaging/internalMethods.test.ts b/packages/extension/src/background/messaging/internalMethods.test.ts index f0781f98..00832749 100644 --- a/packages/extension/src/background/messaging/internalMethods.test.ts +++ b/packages/extension/src/background/messaging/internalMethods.test.ts @@ -1,5 +1,5 @@ import { JsonRpcMethod } from '@algosigner/common/messaging/types'; -import { Ledger } from '@algosigner/common/types'; +import { Network } from '@algosigner/common/types'; import encryptionWrap from '../encryptionWrap'; import { InternalMethods } from './internalMethods'; import algosdk from 'algosdk'; @@ -116,7 +116,7 @@ describe('wallet flow', () => { symbol: undefined, }, ], - ledger: Ledger.MainNet, + ledger: Network.MainNet, wallet: { TestNet: [], MainNet: [], @@ -160,7 +160,7 @@ describe('wallet flow', () => { symbol: undefined, }, ], - ledger: Ledger.MainNet, + ledger: Network.MainNet, wallet: { TestNet: [], MainNet: [], @@ -197,7 +197,7 @@ describe('wallet flow', () => { params: { ...account, mnemonic: mnemonic, - ledger: Ledger.TestNet, + ledger: Network.TestNet, }, id: '17402bbaa89', }, @@ -238,7 +238,7 @@ describe('wallet flow', () => { params: { name: testImportAccount.name, mnemonic: testImportAccount.mnemonic, - ledger: Ledger.TestNet, + ledger: Network.TestNet, }, id: '17402bbaa89', }, @@ -283,7 +283,7 @@ describe('wallet flow', () => { method: method, params: { address: testImportAccount.address, - ledger: Ledger.TestNet, + ledger: Network.TestNet, }, id: '17402bbaa89', }, @@ -329,7 +329,7 @@ describe('algosdk methods', () => { method: method, params: { address: testImportAccount.address, - ledger: Ledger.TestNet, + ledger: Network.TestNet, }, id: '17402bbaa89', }, @@ -371,7 +371,7 @@ describe('algosdk methods', () => { method: method, params: { address: testImportAccount.address, - ledger: Ledger.TestNet, + ledger: Network.TestNet, }, id: '17402bbaa89', }, @@ -505,7 +505,7 @@ describe('algosdk methods', () => { method: method, params: { assetId: 12008492, - ledger: Ledger.TestNet, + ledger: Network.TestNet, }, id: '17402bbaa89', }, diff --git a/packages/extension/src/background/messaging/internalMethods.ts b/packages/extension/src/background/messaging/internalMethods.ts index 82200eff..8cc5e83a 100644 --- a/packages/extension/src/background/messaging/internalMethods.ts +++ b/packages/extension/src/background/messaging/internalMethods.ts @@ -2,7 +2,7 @@ import algosdk from 'algosdk'; import { JsonRpcMethod } from '@algosigner/common/messaging/types'; import { logging, LogLevel } from '@algosigner/common/logging'; import { ExtensionStorage } from '@algosigner/storage/src/extensionStorage'; -import { Alias, Ledger, Namespace, NamespaceConfig } from '@algosigner/common/types'; +import { Alias, Network, Namespace, NamespaceConfig } from '@algosigner/common/types'; import { RequestError } from '@algosigner/common/errors'; import { AliasConfig } from '@algosigner/common/config'; import { Task } from './task'; @@ -11,17 +11,17 @@ import { Settings } from '../config'; import encryptionWrap from '../encryptionWrap'; import Session from '../utils/session'; import AssetsDetailsHelper from '../utils/assetsDetailsHelper'; -import { initializeCache, getAvailableLedgersExt } from '../utils/helper'; +import { initializeCache, getAvailableNetworksFromCache } from '../utils/helper'; import { ValidationStatus } from '../utils/validator'; import { calculateEstimatedFee, getValidatedTxnWrap, - getLedgerFromGenesisID, - getLedgerFromMixedGenesis, + getNetworkNameFromGenesisID, + getNetworkFromMixedGenesis, } from '../transaction/actions'; import { BaseValidatedTxnWrap } from '../transaction/baseValidatedTxnWrap'; import { buildTransaction } from '../utils/transactionBuilder'; -import { getBaseSupportedLedgers, LedgerTemplate } from '@algosigner/common/types/ledgers'; +import { getBaseSupportedNetworks, NetworkTemplate } from '@algosigner/common/types/network'; import { extensionBrowser } from '@algosigner/common/chrome'; const session = new Session(); @@ -29,22 +29,22 @@ const session = new Session(); export class InternalMethods { private static _encryptionWrap: encryptionWrap | undefined; - public static getAlgod(ledger: string): algosdk.Algodv2 { - const params = Settings.getBackendParams(ledger, API.Algod); + public static getAlgod(network: string): algosdk.Algodv2 { + const params = Settings.getBackendParams(network, API.Algod); return new algosdk.Algodv2(params.apiKey, params.url, params.port); } - public static getIndexer(ledger: string): algosdk.Indexer { - const params = Settings.getBackendParams(ledger, API.Indexer); + public static getIndexer(network: string): algosdk.Indexer { + const params = Settings.getBackendParams(network, API.Indexer); return new algosdk.Indexer(params.apiKey, params.url, params.port); } private static safeWallet(wallet: any) { - // Intialize the safe wallet then add the wallet ledgers in as empty arrays + // Intialize the safe wallet then add the wallet networks in as empty arrays const safeWallet = {}; Object.keys(wallet).forEach((key) => { safeWallet[key] = []; - // Afterwards we can add in all the non-private keys and names into the safewallet + // Afterwards we can add in all the non-private keys and names into the safeWallet for (let j = 0; j < wallet[key].length; j++) { const { address, name, isRef } = wallet[key][j]; safeWallet[key].push({ @@ -77,19 +77,19 @@ export class InternalMethods { }); } - for (const l in Ledger) { + for (const n in Network) { // Format accounts as aliases - const ledgerAccountAliases = []; - for (const acc of wallet[l]) { - ledgerAccountAliases.push({ + const networkAccountAliases = []; + for (const acc of wallet[n]) { + networkAccountAliases.push({ name: acc.name, address: acc.address, namespace: Namespace.AlgoSigner_Accounts, }); } // Save accounts and contacts as aliases - aliases[l] = { - [Namespace.AlgoSigner_Accounts]: ledgerAccountAliases, + aliases[n] = { + [Namespace.AlgoSigner_Accounts]: networkAccountAliases, [Namespace.AlgoSigner_Contacts]: contactAliases, }; } @@ -98,21 +98,21 @@ export class InternalMethods { }); } - // Checks if an account for the given address exists on AlgoSigner for a given ledger. + // Checks if an account for the given address exists on AlgoSigner for a given network. public static checkAccountIsImported(genesisID: string, address: string): void { - const ledger: string = getLedgerFromGenesisID(genesisID); + const network: string = getNetworkNameFromGenesisID(genesisID); let found = false; - for (let i = session.wallet[ledger].length - 1; i >= 0; i--) { - if (session.wallet[ledger][i].address === address) { + for (let i = session.wallet[network].length - 1; i >= 0; i--) { + if (session.wallet[network][i].address === address) { found = true; break; } } - if (!found) throw RequestError.NoAccountMatch(address, ledger); + if (!found) throw RequestError.NoAccountMatch(address, network); } - private static loadAccountAssetsDetails(address: string, ledger: Ledger) { - const algod = this.getAlgod(ledger); + private static loadAccountAssetsDetails(address: string, network: Network) { + const algod = this.getAlgod(network); algod .accountInformation(address) .do() @@ -120,7 +120,7 @@ export class InternalMethods { if ('assets' in res && res.assets.length > 0) { AssetsDetailsHelper.add( res.assets.map((x) => x['asset-id']), - ledger + network ); } }) @@ -162,7 +162,7 @@ export class InternalMethods { }; extensionStorage.setStorage( 'aliases', - { [Ledger.MainNet]: emptyAliases, [Ledger.MainNet]: emptyAliases }, + { [Network.MainNet]: emptyAliases, [Network.MainNet]: emptyAliases }, null ); const namespaceConfigs: Array = []; @@ -178,17 +178,15 @@ export class InternalMethods { this._encryptionWrap = new encryptionWrap(request.body.params.passphrase); const newWallet = { - [Ledger.MainNet]: [], - [Ledger.TestNet]: [], + [Network.MainNet]: [], + [Network.TestNet]: [], }; this._encryptionWrap?.lock(JSON.stringify(newWallet), (isSuccessful: any) => { if (isSuccessful) { - getAvailableLedgersExt((availableLedgers) => { - session.availableLedgers = availableLedgers; - session.wallet = this.safeWallet(newWallet); - session.ledger = Ledger.MainNet; - sendResponse(session.session); - }); + session.availableLedgers = getBaseSupportedNetworks(); + session.wallet = this.safeWallet(newWallet); + session.ledger = Network.MainNet; + sendResponse(session.session); } else { sendResponse({ error: 'Lock failed' }); } @@ -225,19 +223,19 @@ export class InternalMethods { sendResponse(response); } else { const wallet = this.safeWallet(response); - getAvailableLedgersExt((availableLedgers) => { + getAvailableNetworksFromCache((availableNetworks: Array) => { const extensionStorage = new ExtensionStorage(); // Load Accounts details from Cache - extensionStorage.getStorage('cache', (storedCache: any) => { + extensionStorage.getStorage('cache', async (storedCache: any) => { const cache: Cache = initializeCache(storedCache); - const cachedLedgerAccounts = Object.keys(cache.accounts); - - for (var j = cachedLedgerAccounts.length - 1; j >= 0; j--) { - const ledger = cachedLedgerAccounts[j]; - if (wallet[ledger]) { - for (var i = wallet[ledger].length - 1; i >= 0; i--) { - if (wallet[ledger][i].address in cache.accounts[ledger]) { - wallet[ledger][i].details = cache.accounts[ledger][wallet[ledger][i].address]; + const cachedNetworkAccounts = Object.keys(cache.accounts); + + for (let j = cachedNetworkAccounts.length - 1; j >= 0; j--) { + const network = cachedNetworkAccounts[j]; + if (wallet[network]) { + for (let i = wallet[network].length - 1; i >= 0; i--) { + if (wallet[network][i].address in cache.accounts[network]) { + wallet[network][i].details = cache.accounts[network][wallet[network][i].address]; } } } @@ -245,8 +243,8 @@ export class InternalMethods { // Setup session session.wallet = wallet; - session.ledger = Ledger.MainNet; - session.availableLedgers = availableLedgers; + session.ledger = Network.MainNet; + session.availableLedgers = availableNetworks; // Load internal aliases && namespace configurations this.reloadAliases(); @@ -301,7 +299,7 @@ export class InternalMethods { } public static [JsonRpcMethod.SaveAccount](request: any, sendResponse: Function) { - const { mnemonic, name, ledger, address, passphrase } = request.body.params; + const { mnemonic, name, ledger: network, address, passphrase } = request.body.params; this._encryptionWrap = new encryptionWrap(passphrase); this._encryptionWrap.unlock((unlockedValue: any) => { @@ -314,11 +312,11 @@ export class InternalMethods { name: name, }; - if (!unlockedValue[ledger]) { - unlockedValue[ledger] = []; + if (!unlockedValue[network]) { + unlockedValue[network] = []; } - unlockedValue[ledger].push(newAccount); + unlockedValue[network].push(newAccount); this._encryptionWrap?.lock(JSON.stringify(unlockedValue), (isSuccessful: any) => { if (isSuccessful) { session.wallet = this.safeWallet(unlockedValue); @@ -334,7 +332,7 @@ export class InternalMethods { } public static [JsonRpcMethod.DeleteAccount](request: any, sendResponse: Function) { - const { ledger, address, passphrase } = request.body.params; + const { ledger: network, address, passphrase } = request.body.params; this._encryptionWrap = new encryptionWrap(passphrase); this._encryptionWrap.unlock((unlockedValue: any) => { @@ -342,9 +340,9 @@ export class InternalMethods { sendResponse(unlockedValue); } else { // Find address to delete - for (var i = unlockedValue[ledger].length - 1; i >= 0; i--) { - if (unlockedValue[ledger][i].address === address) { - unlockedValue[ledger].splice(i, 1); + for (var i = unlockedValue[network].length - 1; i >= 0; i--) { + if (unlockedValue[network][i].address === address) { + unlockedValue[network].splice(i, 1); break; } } @@ -363,12 +361,12 @@ export class InternalMethods { } public static [JsonRpcMethod.ImportAccount](request: any, sendResponse: Function) { - const { mnemonic, address, isRef, name, ledger } = request.body.params; + const { mnemonic, address, isRef, name, ledger: network } = request.body.params; this._encryptionWrap = new encryptionWrap(request.body.params.passphrase); let newAccount; try { - const existingAccounts = session.wallet[ledger]; + const existingAccounts = session.wallet[network]; let targetAddress = address; if (!isRef) { @@ -379,11 +377,11 @@ export class InternalMethods { for (let i = 0; i < existingAccounts.length; i++) { if (existingAccounts[i].address === targetAddress) { throw new Error( - `An account with this address already exists in your ${ledger} wallet.` + `An account with this address already exists in your ${network} wallet.` ); } if (existingAccounts[i].name === name) { - throw new Error(`An account named '${name}' already exists in your ${ledger} wallet.`); + throw new Error(`An account named '${name}' already exists in your ${network} wallet.`); } } } @@ -403,14 +401,14 @@ export class InternalMethods { if ('error' in unlockedValue) { sendResponse(unlockedValue); } else { - if (!unlockedValue[ledger]) { - unlockedValue[ledger] = []; + if (!unlockedValue[network]) { + unlockedValue[network] = []; } - unlockedValue[ledger].push(newAccount); + unlockedValue[network].push(newAccount); this._encryptionWrap?.lock(JSON.stringify(unlockedValue), (isSuccessful: any) => { if (isSuccessful) { - this.loadAccountAssetsDetails(newAccount.address, ledger); + this.loadAccountAssetsDetails(newAccount.address, network); session.wallet = this.safeWallet(unlockedValue); this.reloadAliases(); sendResponse(session.wallet); @@ -424,11 +422,11 @@ export class InternalMethods { } public static [JsonRpcMethod.LedgerLinkAddress](request: any, sendResponse: Function) { - const ledger = request.body.params.ledger; + const network = request.body.params.ledger; extensionBrowser.tabs.create( { active: true, - url: extensionBrowser.extension.getURL(`/index.html#/${ledger}/ledger-hardware-connector`), + url: extensionBrowser.extension.getURL(`/index.html#/${network}/ledger-hardware-connector`), }, (tab) => { // Tab object is created here, but extension popover will close. @@ -500,9 +498,9 @@ export class InternalMethods { sendResponse({ message: message }); } else if (session.txnRequest.source === 'ui') { // If this is an UI transaction then we need to submit to the network - const ledger = getLedgerFromGenesisID(decodedTxn.txn.genesisID); + const network = getNetworkNameFromGenesisID(decodedTxn.txn.genesisID); - const algod = this.getAlgod(ledger); + const algod = this.getAlgod(network); algod .sendRawTransaction(txnBuffer) .do() @@ -545,11 +543,11 @@ export class InternalMethods { // Transaction wrap will contain response message if from dApp and structure will be different const txn = session.txnObject.transactionWraps[0].transaction; - const ledger = getLedgerFromMixedGenesis(txn.genesisID, txn.genesisHash); + const network = getNetworkFromMixedGenesis(txn.genesisID, txn.genesisHash); extensionBrowser.tabs.create( { active: true, - url: extensionBrowser.extension.getURL(`/index.html#/${ledger.name}/ledger-hardware-sign`), + url: extensionBrowser.extension.getURL(`/index.html#/${network.name}/ledger-hardware-sign`), }, (tab) => { // Tab object is created here, but extension popover will close. @@ -559,7 +557,7 @@ export class InternalMethods { } public static [JsonRpcMethod.LedgerSaveAccount](request: any, sendResponse: Function) { - const { name, ledger, passphrase } = request.body.params; + const { name, ledger: network, passphrase } = request.body.params; // The value returned from the Ledger device is hex. // This is passed directly to save and needs to be converted. const address = algosdk.encodeAddress(Buffer.from(request.body.params.hexAddress, 'hex')); @@ -575,11 +573,11 @@ export class InternalMethods { isHardware: true, }; - if (!unlockedValue[ledger]) { - unlockedValue[ledger] = []; + if (!unlockedValue[network]) { + unlockedValue[network] = []; } - unlockedValue[ledger].push(newAccount); + unlockedValue[network].push(newAccount); this._encryptionWrap?.lock(JSON.stringify(unlockedValue), (isSuccessful: any) => { if (isSuccessful) { session.wallet = this.safeWallet(unlockedValue); @@ -594,31 +592,31 @@ export class InternalMethods { } public static [JsonRpcMethod.AccountDetails](request: any, sendResponse: Function) { - const { ledger, address } = request.body.params; - const algod = this.getAlgod(ledger); + const { ledger: network, address } = request.body.params; + const algod = this.getAlgod(network); algod .accountInformation(address) .do() .then((res: any) => { const extensionStorage = new ExtensionStorage(); extensionStorage.getStorage('cache', (storedCache: any) => { - const cache: Cache = initializeCache(storedCache, ledger); + const cache: Cache = initializeCache(storedCache, network); // Check for asset details saved in storage, if needed if ('assets' in res && res.assets.length > 0) { const assetDetails = []; for (var i = res.assets.length - 1; i >= 0; i--) { const assetId = res.assets[i]['asset-id']; - if (assetId in cache.assets[ledger]) { + if (assetId in cache.assets[network]) { res.assets[i] = { - ...cache.assets[ledger][assetId], + ...cache.assets[network][assetId], ...res.assets[i], }; } assetDetails.push(assetId); } - if (assetDetails.length > 0) AssetsDetailsHelper.add(assetDetails, ledger); + if (assetDetails.length > 0) AssetsDetailsHelper.add(assetDetails, network); res.assets.sort((a, b) => a['asset-id'] - b['asset-id']); } @@ -626,16 +624,16 @@ export class InternalMethods { sendResponse(res); // Save account updated account details in cache - cache.accounts[ledger][address] = res; + cache.accounts[network][address] = res; extensionStorage.setStorage('cache', cache, null); // Add details to session const wallet = session.wallet; - // Validate the ledger still exists in the wallet - if (ledger && wallet[ledger]) { - for (var i = wallet[ledger].length - 1; i >= 0; i--) { - if (wallet[ledger][i].address === address) { - wallet[ledger][i].details = res; + // Validate the network still exists in the wallet + if (network && wallet[network]) { + for (var i = wallet[network].length - 1; i >= 0; i--) { + if (wallet[network][i].address === address) { + wallet[network][i].details = res; } } } @@ -648,9 +646,9 @@ export class InternalMethods { } public static [JsonRpcMethod.Transactions](request: any, sendResponse: Function) { - const { ledger, address, limit, 'next-token': token } = request.body.params; - const indexer = this.getIndexer(ledger); - const algod = this.getAlgod(ledger); + const { ledger: network, address, limit, 'next-token': token } = request.body.params; + const indexer = this.getIndexer(network); + const algod = this.getAlgod(network); const txList = indexer.lookupAccountTransactions(address); const pendingTxList = algod.pendingTransactionByAddress(address); if (limit) txList.limit(limit); @@ -670,7 +668,7 @@ export class InternalMethods { // asset or appl id const id = pend['txn']['xaid'] || pend['txn']['faid'] || pend['txn']['apid']; const cachedAsset = - (pend['txn']['xaid'] || pend['txn']['faid']) && cache.assets[ledger][id]; + (pend['txn']['xaid'] || pend['txn']['faid']) && cache.assets[network][id]; const assetName = cachedAsset && (cachedAsset['unit-name'] || cachedAsset['name'] || cachedAsset['asset-id']); @@ -702,9 +700,9 @@ export class InternalMethods { } public static [JsonRpcMethod.AssetDetails](request: any, sendResponse: Function) { + const { ledger: network } = request.body.params; const assetId = request.body.params['asset-id']; - const { ledger } = request.body.params; - const indexer = this.getIndexer(ledger); + const indexer = this.getIndexer(network); indexer .lookupAssetByID(assetId) .do() @@ -714,10 +712,10 @@ export class InternalMethods { const extensionStorage = new ExtensionStorage(); extensionStorage.getStorage('cache', (cache: any) => { if (cache === undefined) cache = new Cache(); - if (!(ledger in cache.assets)) cache.assets[ledger] = {}; + if (!(network in cache.assets)) cache.assets[network] = {}; - if (!(assetId in cache.assets[ledger])) { - cache.assets[ledger][assetId] = res.asset.params; + if (!(assetId in cache.assets[network])) { + cache.assets[network][assetId] = res.asset.params; extensionStorage.setStorage('cache', cache, null); } }); @@ -752,8 +750,8 @@ export class InternalMethods { }); } - const { ledger, filter, nextToken } = request.body.params; - const indexer = this.getIndexer(ledger); + const { ledger: network, filter, nextToken } = request.body.params; + const indexer = this.getIndexer(network); // Do the search for asset id (if filter value is integer) // and asset name and concat them. if (filter.length > 0 && !isNaN(filter) && (!nextToken || nextToken.length === 0)) { @@ -774,9 +772,9 @@ export class InternalMethods { } public static [JsonRpcMethod.AssetsVerifiedList](request: any, sendResponse: Function) { - const { ledger } = request.body.params; + const { ledger: network } = request.body.params; - if (ledger === Ledger.MainNet) { + if (network === Network.MainNet) { fetch('https://mobile-api.algorand.com/api/assets/?status=verified') .then((response) => { return response.json().then((json) => { @@ -799,9 +797,9 @@ export class InternalMethods { } public static [JsonRpcMethod.SignSendTransaction](request: any, sendResponse: Function) { - const { ledger, address, passphrase, txnParams } = request.body.params; + const { ledger: network, address, passphrase, txnParams } = request.body.params; this._encryptionWrap = new encryptionWrap(passphrase); - const algod = this.getAlgod(ledger); + const algod = this.getAlgod(network); this._encryptionWrap.unlock(async (unlockedValue: any) => { if ('error' in unlockedValue) { @@ -822,13 +820,13 @@ export class InternalMethods { if ('note' in txn) txn.note = new Uint8Array(Buffer.from(txn.note)); let account; - const authAddr = await Task.getChainAuthAddress({ address, ledger }); + const authAddr = await Task.getChainAuthAddress({ address, network }); const signAddress = authAddr || address; // Find address to send algos from - for (var i = unlockedValue[ledger].length - 1; i >= 0; i--) { - if (unlockedValue[ledger][i].address === signAddress) { - account = unlockedValue[ledger][i]; + for (var i = unlockedValue[network].length - 1; i >= 0; i--) { + if (unlockedValue[network][i].address === signAddress) { + account = unlockedValue[network][i]; break; } } @@ -915,7 +913,7 @@ export class InternalMethods { return false; } - this.getAlgod(ledger) + this.getAlgod(network) .sendRawTransaction(signedTxn.blob) .do() .then((resp: any) => { @@ -941,30 +939,32 @@ export class InternalMethods { return true; } - public static [JsonRpcMethod.ChangeLedger](request: any, sendResponse: Function) { + public static [JsonRpcMethod.ChangeNetwork](request: any, sendResponse: Function) { session.ledger = request.body.params['ledger']; sendResponse({ ledger: session.ledger }); } public static [JsonRpcMethod.DeleteNetwork](request: any, sendResponse: Function) { - const ledger = request.body.params['name']; - const ledgerUniqueName = ledger.toLowerCase(); - getAvailableLedgersExt((availiableLedgers) => { - const matchingLedger = availiableLedgers.find((avls) => avls.uniqueName === ledgerUniqueName); + const network = request.body.params['name']; + const networkUniqueName = network.toLowerCase(); + getAvailableNetworksFromCache((availiableNetworks) => { + const matchingNetwork = availiableNetworks.find( + (network) => network.uniqueName === networkUniqueName + ); - if (!matchingLedger || !matchingLedger.isEditable) { - sendResponse({ error: 'This ledger can not be deleted.' }); + if (!matchingNetwork || !matchingNetwork.isEditable) { + sendResponse({ error: 'This network can not be deleted.' }); } else { - // Delete ledger from availableLedgers and assign to new array // - const remainingLedgers = availiableLedgers.filter( - (avls) => avls.uniqueName !== ledgerUniqueName + // Delete network from availableNetworks and assign to new array + const remainingNetworks = availiableNetworks.filter( + (network) => network.uniqueName !== networkUniqueName ); - // Delete Accounts from wallet // + // Delete Accounts from wallet this._encryptionWrap = new encryptionWrap(request.body.params.passphrase); // Remove existing accoutns in session.wallet - var existingAccounts = session.wallet[request.body.params['ledger']]; + const existingAccounts = session.wallet[request.body.params['ledger']]; if (existingAccounts) { delete session.wallet[request.body.params['ledger']]; } @@ -974,9 +974,9 @@ export class InternalMethods { if ('error' in unlockedValue) { sendResponse(unlockedValue); } else { - if (unlockedValue[ledger]) { - // The unlocked value contains ledger information - delete it - delete unlockedValue[ledger]; + if (unlockedValue[network]) { + // The unlocked value contains network information - delete it + delete unlockedValue[network]; } // Resave the updated wallet value @@ -988,25 +988,25 @@ export class InternalMethods { } }) .then(() => { - // Update cache with remaining ledgers// + // Update cache with remaining networks const extensionStorage = new ExtensionStorage(); extensionStorage.getStorage('cache', (cache: any) => { if (cache === undefined) { cache = initializeCache(cache); } if (cache) { - cache.availableLedgers = remainingLedgers; + cache.availableLedgers = remainingNetworks; extensionStorage.setStorage('cache', cache, () => {}); } - // Update the session // - session.availableLedgers = remainingLedgers; + // Update the session + session.availableLedgers = remainingNetworks; - // Delete from the injected ledger settings // - Settings.deleteInjectedNetwork(ledgerUniqueName); + // Delete from the injected network settings + Settings.deleteInjectedNetwork(networkUniqueName); - // Send back remaining ledgers // - sendResponse({ availableLedgers: remainingLedgers }); + // Send back remaining networks + sendResponse({ availableNetworks: remainingNetworks }); }); }); } @@ -1039,10 +1039,10 @@ export class InternalMethods { // We have evaluated the passphrase and it was valid. }); } - + const previousName = params['previousName'].toLowerCase(); const targetName = previousName ? previousName : params['name'].toLowerCase(); - const addedLedger = new LedgerTemplate({ + const addedNetwork = new NetworkTemplate({ name: params['name'], genesisID: params['genesisID'], genesisHash: params['genesisHash'], @@ -1052,44 +1052,44 @@ export class InternalMethods { headers: params['headers'], }); - // Specifically get the base ledgers to check and prevent them from being overriden. - const defaultLedgers = getBaseSupportedLedgers(); + // Specifically get the base networks to check and prevent them from being overriden. + const defaultNetworks = getBaseSupportedNetworks(); - getAvailableLedgersExt((availiableLedgers) => { - const comboLedgers = [...availiableLedgers]; + getAvailableNetworksFromCache((cacheNetworks) => { + const availableNetworks = [...cacheNetworks]; - // Add the new ledger if it isn't there. - if (!comboLedgers.some((cledg) => cledg.uniqueName === targetName)) { - comboLedgers.push(addedLedger); + // Add the new network if it isn't there. + if (!availableNetworks.some((network) => network.uniqueName === targetName)) { + availableNetworks.push(addedNetwork); - // Also add the ledger to the injected ledgers in settings - Settings.addInjectedNetwork(addedLedger); + // Also add the network to the injected networks in settings + Settings.addInjectedNetwork(addedNetwork); } else { - // If the new ledger name does exist, we sould update the values as long as it is not a default ledger. - const matchingLedger = comboLedgers.find((cledg) => cledg.uniqueName === targetName); - if (!defaultLedgers.some((dledg) => dledg.uniqueName === matchingLedger.uniqueName)) { - Settings.updateInjectedNetwork(addedLedger, previousName); - matchingLedger.name = addedLedger.name; - matchingLedger.genesisID = addedLedger.genesisID; - matchingLedger.symbol = addedLedger.symbol; - matchingLedger.genesisHash = addedLedger.genesisHash; - matchingLedger.algodUrl = addedLedger.algodUrl; - matchingLedger.indexerUrl = addedLedger.indexerUrl; - matchingLedger.headers = addedLedger.headers; + // If the new network name does exist, we sould update the values as long as it is not a default network. + const matchingNetwork = availableNetworks.find((network) => network.uniqueName === targetName); + if (!defaultNetworks.some((network) => network.uniqueName === matchingNetwork.uniqueName)) { + Settings.updateInjectedNetwork(addedNetwork, previousName); + matchingNetwork.name = addedNetwork.name; + matchingNetwork.genesisID = addedNetwork.genesisID; + matchingNetwork.symbol = addedNetwork.symbol; + matchingNetwork.genesisHash = addedNetwork.genesisHash; + matchingNetwork.algodUrl = addedNetwork.algodUrl; + matchingNetwork.indexerUrl = addedNetwork.indexerUrl; + matchingNetwork.headers = addedNetwork.headers; } } // Update the session and send response before setting cache. - session.availableLedgers = comboLedgers; - sendResponse({ availableLedgers: comboLedgers }); + session.availableLedgers = availableNetworks; + sendResponse({ availableLedgers: availableNetworks }); - // Updated the cached ledgers. + // Updated the cached networks. const extensionStorage = new ExtensionStorage(); extensionStorage.getStorage('cache', (cache: any) => { if (cache === undefined) { cache = initializeCache(cache); } if (cache) { - cache.availableLedgers = comboLedgers; + cache.availableLedgers = availableNetworks; extensionStorage.setStorage('cache', cache, () => {}); } }); @@ -1100,9 +1100,9 @@ export class InternalMethods { } } - public static [JsonRpcMethod.GetLedgers](request: any, sendResponse: Function) { - getAvailableLedgersExt((availableLedgers) => { - sendResponse(availableLedgers); + public static [JsonRpcMethod.GetNetworks](request: any, sendResponse: Function) { + getAvailableNetworksFromCache((availableNetworks) => { + sendResponse(availableNetworks); }); return true; @@ -1177,14 +1177,14 @@ export class InternalMethods { } public static [JsonRpcMethod.GetAliasedAddresses](request: any, sendResponse: Function) { - const { ledger, searchTerm } = request.body.params; + const { ledger: network, searchTerm } = request.body.params; // Check if the term matches any of our namespaces - const matchingNamespaces: Array = AliasConfig.getMatchingNamespaces(ledger); + const matchingNamespaces: Array = AliasConfig.getMatchingNamespaces(network); const extensionStorage = new ExtensionStorage(); extensionStorage.getStorage('aliases', async (aliases: any) => { - // aliases: { ledger: { namespace: [...aliases] } } + // aliases: { network: { namespace: [...aliases] } } await extensionStorage.getStorage( 'namespaces', async (storedConfigs: Array) => { @@ -1198,8 +1198,8 @@ export class InternalMethods { const apiFetches = []; for (const namespace of matchingNamespaces) { const aliasesMatchingInNamespace: Array = []; - if (aliases[ledger][namespace]) { - for (const alias of aliases[ledger][namespace]) { + if (aliases[network][namespace]) { + for (const alias of aliases[network][namespace]) { if (alias.name.toLowerCase().includes(searchTerm.toLowerCase())) { aliasesMatchingInNamespace.push({ name: alias.name, @@ -1214,11 +1214,11 @@ export class InternalMethods { if ( searchTerm.length && availableExternalNamespaces.includes(namespace) && - AliasConfig[namespace].ledgers && - AliasConfig[namespace].ledgers[ledger]?.length > 0 + AliasConfig[namespace].networks && + AliasConfig[namespace].networks[network]?.length > 0 ) { // If we find enabled external namespaces, we prepare an API fetch - const apiURL = AliasConfig[namespace].ledgers[ledger].replace('${term}', searchTerm); + const apiURL = AliasConfig[namespace].networks[network].replace('${term}', searchTerm); const apiTimeout = AliasConfig[namespace].apiTimeout; // We set a max timeout for each call diff --git a/packages/extension/src/background/messaging/task.ts b/packages/extension/src/background/messaging/task.ts index e157b029..d226f3ee 100644 --- a/packages/extension/src/background/messaging/task.ts +++ b/packages/extension/src/background/messaging/task.ts @@ -1,14 +1,14 @@ import algosdk, { MultisigMetadata, Transaction } from 'algosdk'; -import { OptsKeys, WalletTransaction } from '@algosigner/common/types'; +import { NetworkSelectionType, OptsKeys, WalletTransaction } from '@algosigner/common/types'; import { RequestError } from '@algosigner/common/errors'; import { JsonRpcMethod } from '@algosigner/common/messaging/types'; -import { Ledger } from '@algosigner/common/types'; +import { Network } from '@algosigner/common/types'; import { API } from './types'; import { getValidatedTxnWrap, - getLedgerFromGenesisID, - getLedgerFromMixedGenesis, + getNetworkNameFromGenesisID, + getNetworkFromMixedGenesis, } from '../transaction/actions'; import { BaseValidatedTxnWrap } from '../transaction/baseValidatedTxnWrap'; import { ValidationResponse, ValidationStatus } from '../utils/validator'; @@ -20,6 +20,7 @@ import { extensionBrowser } from '@algosigner/common/chrome'; import { logging, LogLevel } from '@algosigner/common/logging'; import { base64ToByteArray, byteArrayToBase64 } from '@algosigner/common/encoding'; import { areBuffersEqual } from '@algosigner/common/utils'; +import { NetworkTemplate } from '@algosigner/common/types/network'; // Popup properties accounts for additional space needed for the title bar const titleBarHeight = 28; @@ -62,16 +63,24 @@ export class Task { } // Checks for the originId authorization in details then call to make sure the account exists in Algosigner. - private static checkAccountIsImportedAndAuthorized(ledger: string, genesisID: string, genesisHash: string, address: string, origin: string): void { + private static checkAccountIsImportedAndAuthorized( + network: string, + genesisID: string, + genesisHash: string, + address: string, + origin: string + ): void { // Legacy authorized and internal calls will not have an origin in authorized pool details if (Task.authorized_pool_details[origin]) { // This must be a dApp using enable - verify the ledger and address are authorized - if ((Task.authorized_pool_details[origin]['genesisID'] !== genesisID) - || (Task.authorized_pool_details[origin]['ledger'] !== ledger) - || (genesisHash && Task.authorized_pool_details[origin]['genesisHash'] !== genesisHash) - || (!Task.authorized_pool_details[origin]['accounts'].includes(address))) { - throw RequestError.NoAccountMatch(address, ledger); - } + if ( + Task.authorized_pool_details[origin]['genesisID'] !== genesisID || + Task.authorized_pool_details[origin]['ledger'] !== network || + (genesisHash && Task.authorized_pool_details[origin]['genesisHash'] !== genesisHash) || + !Task.authorized_pool_details[origin]['accounts'].includes(address) + ) { + throw RequestError.NoAccountMatch(address, network); + } } // Call the normal account check InternalMethods.checkAccountIsImported(genesisID, address); @@ -103,10 +112,10 @@ export class Task { public static getChainAuthAddress = async (transaction: any): Promise => { // The ledger and address will be provided differently from UI and dapp - const ledger = transaction.ledger || getLedgerFromGenesisID(transaction.genesisID); + const network = transaction.network || getNetworkNameFromGenesisID(transaction.genesisID); const address = transaction.address || transaction.from; - const conn = Settings.getBackendParams(ledger, API.Algod); + const conn = Settings.getBackendParams(network, API.Algod); const sendPath = `/v2/accounts/${address}`; const fetchParams: any = { headers: { @@ -157,8 +166,8 @@ export class Task { if (transactionWrap.transaction['type'] === 'axfer') { const assetIndex = transactionWrap.transaction['assetIndex']; - const ledger = getLedgerFromGenesisID(transactionWrap.transaction['genesisID']); - const conn = Settings.getBackendParams(ledger, API.Algod); + const network = getNetworkNameFromGenesisID(transactionWrap.transaction['genesisID']); + const conn = Settings.getBackendParams(network, API.Algod); const sendPath = `/v2/assets/${assetIndex}`; const fetchAssets: any = { headers: { @@ -307,8 +316,10 @@ export class Task { throw RequestError.InvalidFields(invalidKeys); } - const ledger = getLedgerFromMixedGenesis(wrap.transaction.genesisID, wrap.transaction.genesisHash); - + const network = getNetworkFromMixedGenesis( + wrap.transaction.genesisID, + wrap.transaction.genesisHash + ); const signers: Array = walletTransactions[index].signers; const signedTxn: string = walletTransactions[index].stxn; const msigData: MultisigMetadata = walletTransactions[index].msig; @@ -322,7 +333,13 @@ export class Task { if (!algosdk.isValidAddress(authAddr)) { throw RequestError.InvalidAuthAddress(authAddr); } - Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisID, ledger.genesisHash, authAddr, request.origin); + Task.checkAccountIsImportedAndAuthorized( + network.name, + network.genesisID, + network.genesisHash, + authAddr, + request.origin + ); } // If we have msigData, we validate the addresses and fetch the resulting msig address @@ -387,7 +404,13 @@ export class Task { // We make sure we have the available accounts for signing signers.forEach((address) => { try { - Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisID, ledger.genesisHash, address, request.origin); + Task.checkAccountIsImportedAndAuthorized( + network.name, + network.genesisID, + network.genesisHash, + address, + request.origin + ); } catch (e) { throw RequestError.CantMatchMsigSigners(e.message); } @@ -416,7 +439,13 @@ export class Task { } else { // There's no signers field, we validate the sender if there's no msig if (!msigData) { - Task.checkAccountIsImportedAndAuthorized(ledger.name, ledger.genesisID, ledger.genesisHash, wrap.transaction.from, request.origin); + Task.checkAccountIsImportedAndAuthorized( + network.name, + network.genesisID, + network.genesisHash, + wrap.transaction.from, + request.origin + ); } } @@ -461,7 +490,7 @@ export class Task { throw RequestError.SigningValidationError(code, data); } else { - /** + /** * Group validations */ const providedGroupId: string = transactionWraps[0].transaction.group; @@ -539,7 +568,7 @@ export class Task { } logging.log(errorResponse); - request.error = errorResponse + request.error = errorResponse; // Clean class saved request delete Task.requests[request.originTabID]; @@ -596,24 +625,24 @@ export class Task { delete Task.requests[d.originTabID]; // Set a flag for a specified network - let networkSpecifiedType = 0; + let specifiedNetworkType: NetworkSelectionType = NetworkSelectionType.NoneProvided; if (genesisID && genesisHash) { - networkSpecifiedType = 1; - } else if (genesisID || genesisHash) { - networkSpecifiedType = 2; + specifiedNetworkType = NetworkSelectionType.BothProvided; + } else if (genesisID) { + specifiedNetworkType = NetworkSelectionType.OnlyIDProvided; } - d.body.params.networkSpecifiedType = networkSpecifiedType; + d.body.params.specifiedNetworkType = specifiedNetworkType; // Get ledger/hash/id from the genesisID and/or hash - const ledgerTemplate = getLedgerFromMixedGenesis(genesisID, genesisHash); + const network: NetworkTemplate = getNetworkFromMixedGenesis(genesisID, genesisHash); // Validate that the genesis id and hash if provided match the resulting one // This is because a dapp may request an id and hash from different ledgers if ( - (genesisID && genesisID !== ledgerTemplate.genesisID) || + (genesisID && genesisID !== network.genesisID) || (genesisHash && - ledgerTemplate.genesisHash && - genesisHash !== ledgerTemplate.genesisHash) + network.genesisHash && + genesisHash !== network.genesisHash) ) { d.error = RequestError.UnsupportedNetwork; setTimeout(() => { @@ -624,9 +653,9 @@ export class Task { // We've validated the ledger information // So we can set the ledger, genesisID, and genesisHash - const ledger = ledgerTemplate.name; - genesisID = ledgerTemplate.genesisID; - genesisHash = ledgerTemplate.genesisHash; + const ledger = network.name; + genesisID = network.genesisID; + genesisHash = network.genesisHash; // Then reflect those changes for the page d.body.params.ledger = ledger; @@ -874,8 +903,8 @@ export class Task { } else { const genesisID = Task.authorized_pool_details[d.origin]['genesisID']; const genesisHash = Task.authorized_pool_details[d.origin]['genesisHash']; - const ledger = getLedgerFromMixedGenesis(genesisID, genesisHash).name; - const conn = Settings.getBackendParams(ledger, API.Algod); + const network = getNetworkFromMixedGenesis(genesisID, genesisHash).name; + const conn = Settings.getBackendParams(network, API.Algod); const sendPath = '/v2/transactions'; const fetchParams: any = { headers: { @@ -894,7 +923,7 @@ export class Task { }); Promise.allSettled(fetchPromises).then((results) => { - const algod = InternalMethods.getAlgod(ledger); + const algod = InternalMethods.getAlgod(network); const confirmationPromises = []; results.forEach((res, index) => { if (res.status === 'fulfilled') { @@ -1007,9 +1036,12 @@ export class Task { const session = InternalMethods.getHelperSession(); // If we don't have a ledger requested, respond with an error giving available ledgers if (!d.body.params?.ledger) { - const baseLedgers = Object.keys(Ledger); + const baseNetworks = Object.keys(Network); const injectedNetworks = Settings.getCleansedInjectedNetworks(); - d.error = RequestError.NoLedgerProvided(baseLedgers.toString(), JSON.stringify(injectedNetworks)); + d.error = RequestError.NoLedgerProvided( + baseNetworks.toString(), + JSON.stringify(injectedNetworks) + ); reject(d); return; } @@ -1113,7 +1145,7 @@ export class Task { const signErrors: Array = []; try { - const ledger = getLedgerFromGenesisID(transactionObjs[0].genesisID); + const network = getNetworkNameFromGenesisID(transactionObjs[0].genesisID); const neededAccounts: Array = []; walletTransactions.forEach((w, i) => { const msig = w.msig; @@ -1157,7 +1189,7 @@ export class Task { extensionBrowser.windows.remove(auth.window_id); const recoveredAccounts = []; - if (unlockedValue[ledger] === undefined) { + if (unlockedValue[network] === undefined) { delete Task.requests[responseOriginTabID]; message.error = RequestError.UnsupportedNetwork; MessageApi.send(message); @@ -1169,17 +1201,17 @@ export class Task { // Find addresses to send algos from // We store them using the public address as dictionary key let addressWithNoMnemonic = ''; - for (let i = unlockedValue[ledger].length - 1; i >= 0; i--) { - const account = unlockedValue[ledger][i]; + for (let i = unlockedValue[network].length - 1; i >= 0; i--) { + const account = unlockedValue[network][i]; if (neededAccounts.includes(account.address)) { if (!account.isHardware) { // Check for an address that we were expected but unable to sign with - if (!unlockedValue[ledger][i].mnemonic) { + if (!unlockedValue[network][i].mnemonic) { addressWithNoMnemonic = account.address; break; } recoveredAccounts[account.address] = algosdk.mnemonicToSecretKey( - unlockedValue[ledger][i].mnemonic + unlockedValue[network][i].mnemonic ); } else { hardwareAccounts.push(account.address); @@ -1194,7 +1226,7 @@ export class Task { return; } - if (hardwareAccounts.length){ + if (hardwareAccounts.length) { message.body.params.ledgerIndexes = []; message.body.params.currentLedgerTransaction = 0; } @@ -1332,7 +1364,7 @@ export class Task { } // Clean class saved request delete Task.requests[responseOriginTabID]; - + // Hardware signing will defer the response if (!holdResponse) { if (opts && opts[OptsKeys.sendTxns]) { @@ -1426,8 +1458,8 @@ export class Task { [JsonRpcMethod.SignSendTransaction]: (request: any, sendResponse: Function) => { return InternalMethods[JsonRpcMethod.SignSendTransaction](request, sendResponse); }, - [JsonRpcMethod.ChangeLedger]: (request: any, sendResponse: Function) => { - return InternalMethods[JsonRpcMethod.ChangeLedger](request, sendResponse); + [JsonRpcMethod.ChangeNetwork]: (request: any, sendResponse: Function) => { + return InternalMethods[JsonRpcMethod.ChangeNetwork](request, sendResponse); }, [JsonRpcMethod.SaveNetwork]: (request: any, sendResponse: Function) => { return InternalMethods[JsonRpcMethod.SaveNetwork](request, sendResponse); @@ -1480,8 +1512,8 @@ export class Task { [JsonRpcMethod.DeleteNetwork]: (request: any, sendResponse: Function) => { return InternalMethods[JsonRpcMethod.DeleteNetwork](request, sendResponse); }, - [JsonRpcMethod.GetLedgers]: (request: any, sendResponse: Function) => { - return InternalMethods[JsonRpcMethod.GetLedgers](request, sendResponse); + [JsonRpcMethod.GetNetworks]: (request: any, sendResponse: Function) => { + return InternalMethods[JsonRpcMethod.GetNetworks](request, sendResponse); }, [JsonRpcMethod.LedgerLinkAddress]: (request: any, sendResponse: Function) => { return InternalMethods[JsonRpcMethod.LedgerLinkAddress](request, sendResponse); diff --git a/packages/extension/src/background/messaging/types.ts b/packages/extension/src/background/messaging/types.ts index 07826ffe..8bf03ba1 100644 --- a/packages/extension/src/background/messaging/types.ts +++ b/packages/extension/src/background/messaging/types.ts @@ -1,6 +1,4 @@ -/* eslint-disable @typescript-eslint/ban-types */ - -import { LedgerTemplate } from '@algosigner/common/types/ledgers'; +import { NetworkTemplate } from '@algosigner/common/types/network'; // Key and value must match in this enum so we // can compare its existance with i.e. "TestNet" in SupportedLedger @@ -18,31 +16,32 @@ export enum API { export interface Cache { /* - assets: { - ledger: [ - assetId: { - ... - }, + assets: { + network: [ + assetId: { ... - ], + }, ... - }, - accounts: { - ledger: [ - address: { - ... - }, + ], + ... + }, + accounts: { + network: [ + address: { ... - ], - ... - }, - availableLedgers: [ - { + }, ... - } ], - */ + ... + }, + availableLedgers: [ + { + ... + } + ], + */ assets: object; accounts: object; - availableLedgers: Array; + // Legacy name, renaming could break stored caches + availableLedgers: Array; } diff --git a/packages/extension/src/background/transaction/actions.ts b/packages/extension/src/background/transaction/actions.ts index 38cf9335..f199b71a 100644 --- a/packages/extension/src/background/transaction/actions.ts +++ b/packages/extension/src/background/transaction/actions.ts @@ -25,7 +25,7 @@ import { TransactionType } from '@algosigner/common/types/transaction'; import { RequestError } from '@algosigner/common/errors'; import { BaseValidatedTxnWrap } from './baseValidatedTxnWrap'; import { Settings } from '../config'; -import { getBaseSupportedLedgers, LedgerTemplate } from '@algosigner/common/types/ledgers'; +import { getBaseSupportedNetworks, NetworkTemplate } from '@algosigner/common/types/network'; import { removeEmptyFields } from '@algosigner/common/utils'; import algosdk from 'algosdk'; @@ -143,61 +143,61 @@ export function getValidatedTxnWrap( return validatedTxnWrap; } -export function getLedgerFromGenesisID(genesisID: string): string { - // Default the ledger to mainnet - const defaultLedger = 'MainNet'; +export function getNetworkNameFromGenesisID(genesisID: string): string { + // Default the network to mainnet + const defaultNetwork = 'MainNet'; - // Check Genesis ID for base supported ledgers first - const defaultLedgers = getBaseSupportedLedgers(); - let ledger = defaultLedgers.find((l) => genesisID === l['genesisID']); - if (ledger !== undefined) { - return ledger.name; + // Check Genesis ID for base networks first + const defaultNetworks = getBaseSupportedNetworks(); + let network = defaultNetworks.find((n) => genesisID === n['genesisID']); + if (network !== undefined) { + return network.name; } // Injected networks may have additional information, multiples, or additional checks // so we will check them separately - ledger = Settings.getCleansedInjectedNetworks().find((l) => genesisID === l['genesisID']); - if (ledger !== undefined) { - return ledger.name; + network = Settings.getCleansedInjectedNetworks().find((n) => genesisID === n['genesisID']); + if (network !== undefined) { + return network.name; } - return defaultLedger; + return defaultNetwork; } -export function getLedgerFromMixedGenesis(genesisID: string, genesisHash?: string): LedgerTemplate { - // Check Genesis Id and Hash for base supported ledgers first - const defaultLedgers = getBaseSupportedLedgers(); - let ledger; +export function getNetworkFromMixedGenesis(genesisID: string, genesisHash?: string): NetworkTemplate { + // Check Genesis Id and Hash for base networks first + const defaultNetworks = getBaseSupportedNetworks(); + let network; if (genesisID) { - ledger = defaultLedgers.find((l) => genesisID === l['genesisID']); - if (ledger !== undefined) { + network = defaultNetworks.find((network) => genesisID === network['genesisID']); + if (network !== undefined) { // Found genesisID, make sure the hash matches - if (!genesisHash || genesisHash === ledger.genesisHash) { - return ledger; + if (!genesisHash || genesisHash === network.genesisHash) { + return network; } } // Injected networks may have additional validations so we check them separately const injectedNetworks = Settings.getCleansedInjectedNetworks(); - ledger = injectedNetworks.find((network) => network['genesisID'] === genesisID); - if (ledger) { - return ledger; + network = injectedNetworks.find((network) => network['genesisID'] === genesisID); + if (network) { + return network; } } // We didn't match on the genesis id so check the hashes if (genesisHash) { - ledger = defaultLedgers.find((l) => genesisHash === l['genesisHash']); - if (ledger !== undefined) { + network = defaultNetworks.find((n) => genesisHash === n['genesisHash']); + if (network !== undefined) { // Found genesisHash, make sure the id matches - if (!genesisID || genesisID === ledger.genesisID) { - return ledger; + if (!genesisID || genesisID === network.genesisID) { + return network; } } // We don't currently store the genesisHash of the custom networks } - // Default the ledger to mainnet - return defaultLedgers.find((l) => 'MainNet' === l['name']); + // Default the network to mainnet + return defaultNetworks.find((n) => 'MainNet' === n['name']); } export function calculateEstimatedFee(transactionWrap: BaseValidatedTxnWrap, params: any): void { @@ -206,14 +206,14 @@ export function calculateEstimatedFee(transactionWrap: BaseValidatedTxnWrap, par let estimatedFee = +transaction['fee']; if (transaction['flatFee']) { // If flatFee is enabled, we compare the presented fee by the dApp against the - // Ledger suggested min-fee and use the min-fee if higher than the presented fee + // Network suggested min-fee and use the min-fee if higher than the presented fee if (estimatedFee < minFee) { estimatedFee = minFee; } } else { const dappFee = estimatedFee; if (dappFee === 0) { - // If the dApp doesn't suggest a per-byte fee, we use the Ledger suggested total min-fee + // If the dApp doesn't suggest a per-byte fee, we use the network suggested total min-fee estimatedFee = minFee; } else { /* diff --git a/packages/extension/src/background/utils/assetsDetailsHelper.ts b/packages/extension/src/background/utils/assetsDetailsHelper.ts index 793d9d3d..a06e4d7c 100644 --- a/packages/extension/src/background/utils/assetsDetailsHelper.ts +++ b/packages/extension/src/background/utils/assetsDetailsHelper.ts @@ -1,5 +1,5 @@ import { ExtensionStorage } from '@algosigner/storage/src/extensionStorage'; -import { Ledger } from '@algosigner/common/types'; +import { Network } from '@algosigner/common/types'; import { InternalMethods } from '../messaging/internalMethods'; import { Cache } from '../messaging/types'; import { initializeCache } from './helper'; @@ -11,16 +11,16 @@ const TIMEOUT = 500; /// export default class AssetsDetailsHelper { private static assetsToAdd: { [key: string]: Array } = { - [Ledger.TestNet]: [], - [Ledger.MainNet]: [], + [Network.TestNet]: [], + [Network.MainNet]: [], }; private static timeouts = { - [Ledger.TestNet]: null, - [Ledger.MainNet]: null, + [Network.TestNet]: null, + [Network.MainNet]: null, }; - public static add(assets: Array, ledger: Ledger) { + public static add(assets: Array, ledger: Network) { // If this ledger doesn't have assets yet, then default them to an array if (this.assetsToAdd[ledger] === undefined) { this.assetsToAdd[ledger] = []; @@ -31,7 +31,7 @@ export default class AssetsDetailsHelper { this.timeouts[ledger] = setTimeout(() => this.run(ledger), TIMEOUT); } - private static run(ledger: Ledger) { + private static run(ledger: Network) { if (this.assetsToAdd[ledger].length === 0) { this.timeouts[ledger] = null; return; diff --git a/packages/extension/src/background/utils/helper.ts b/packages/extension/src/background/utils/helper.ts index dd577d23..beafa71a 100644 --- a/packages/extension/src/background/utils/helper.ts +++ b/packages/extension/src/background/utils/helper.ts @@ -1,6 +1,6 @@ import algosdk from 'algosdk'; -import { getBaseSupportedLedgers, LedgerTemplate } from '@algosigner/common/types/ledgers'; -import { Ledger } from '@algosigner/common/types'; +import { getBaseSupportedNetworks, NetworkTemplate } from '@algosigner/common/types/network'; +import { Network } from '@algosigner/common/types'; import { ExtensionStorage } from '@algosigner/storage/src/extensionStorage'; import { Settings } from '../config'; import { API, Cache } from '../messaging/types'; @@ -18,7 +18,7 @@ export function getIndexer(ledger: string) { // Helper function to initialize Cache export function initializeCache( c: Cache | undefined = undefined, - ledger: Ledger | undefined = undefined + network: Network | undefined = undefined ): Cache { let cache: Cache; if (c === undefined) { @@ -31,37 +31,41 @@ export function initializeCache( cache = c; } - if (ledger !== undefined) { - if (!(ledger in cache.assets)) cache.assets[ledger] = {}; - if (!(ledger in cache.accounts)) cache.accounts[ledger] = {}; + if (network !== undefined) { + if (!(network in cache.assets)) cache.assets[network] = {}; + if (!(network in cache.accounts)) cache.accounts[network] = {}; } return cache; } -export function getAvailableLedgersExt(callback) { +export function getAvailableNetworksFromCache(callback?: Function): Array | void { // Load Accounts details from Cache - const availableLedgers = getBaseSupportedLedgers(); + const availableNetworks = getBaseSupportedNetworks(); const extensionStorage = new ExtensionStorage(); - extensionStorage.getStorage('cache', (storedCache: any) => { - const cache: Cache = initializeCache(storedCache); - // Add ledgers from cache to the base ledgers - if (cache.availableLedgers && cache.availableLedgers.length > 0) { - // We should reset and update the injected networks to ensure they will be available for use - Settings.backend_settings.InjectedNetworks = {}; + const storedCache = extensionStorage.getStorage('cache'); + const cache: Cache = initializeCache(storedCache); - for (var i = 0; i < cache.availableLedgers.length; i++) { - if ( - !availableLedgers.some( - (e) => e.name.toLowerCase() === cache.availableLedgers[i].name.toLowerCase() - ) - ) { - const ledgerFromCache = new LedgerTemplate(cache.availableLedgers[i]); - Settings.addInjectedNetwork(ledgerFromCache); - availableLedgers.push(ledgerFromCache); - } + // Join networks from cache with the base networks + if (cache.availableLedgers && cache.availableLedgers.length > 0) { + // We should reset and update the injected networks to ensure they will be available for use + Settings.backend_settings.InjectedNetworks = {}; + + for (let i = 0; i < cache.availableLedgers.length; i++) { + if ( + !availableNetworks.some( + (network) => network.name.toLowerCase() === cache.availableLedgers[i].name.toLowerCase() + ) + ) { + const networkFromCache = new NetworkTemplate(cache.availableLedgers[i]); + Settings.addInjectedNetwork(networkFromCache); + availableNetworks.push(networkFromCache); } } - callback(availableLedgers); - }); + } + if (callback) { + callback(availableNetworks); + } else { + return availableNetworks; + } } diff --git a/packages/extension/src/background/utils/validator.ts b/packages/extension/src/background/utils/validator.ts index 3263cec5..25f86ca9 100644 --- a/packages/extension/src/background/utils/validator.ts +++ b/packages/extension/src/background/utils/validator.ts @@ -1,5 +1,5 @@ import algosdk from 'algosdk'; -import { getBaseSupportedLedgers } from '@algosigner/common/types/ledgers'; +import { getBaseSupportedNetworks } from '@algosigner/common/types/network'; import { Settings } from '../config'; export const STRING_MAX_LENGTH = 1000; @@ -202,8 +202,8 @@ export function Validate(field: any, value: any): ValidationResponse { // Genesis ID must be present and one of the approved values case 'genesisID': if ( - getBaseSupportedLedgers().some((l) => value === l['genesisID']) || - Settings.getCleansedInjectedNetworks().find((l) => value === l['genesisID']) + getBaseSupportedNetworks().some((n) => value === n['genesisID']) || + Settings.getCleansedInjectedNetworks().find((n) => value === n['genesisID']) ) { return new ValidationResponse({ status: ValidationStatus.Valid }); } else { diff --git a/packages/storage/src/extensionStorage.ts b/packages/storage/src/extensionStorage.ts index 87a690a2..0f4b98b7 100644 --- a/packages/storage/src/extensionStorage.ts +++ b/packages/storage/src/extensionStorage.ts @@ -36,9 +36,13 @@ export class ExtensionStorage { // Callback: Callback will return a boolean of true if an account exists // or false if no account is present. /// - public getStorage(objectName: string, callback: Function) { + public getStorage(objectName: string, callback?: Function): any | void { extensionBrowser.storage.local.get([objectName], (result: any) => { - callback && callback(result[objectName]); + if (callback) { + callback(result[objectName]) + } else { + return result[objectName]; + } }); } diff --git a/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts b/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts index 114b6f17..87bebf57 100644 --- a/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts +++ b/packages/ui/src/components/LedgerDevice/LedgerHardwareSign.ts @@ -3,7 +3,7 @@ import { html } from 'htm/preact'; import { useContext, useEffect, useState } from 'preact/hooks'; import { JsonRpcMethod } from '@algosigner/common/messaging/types'; -import { getBaseSupportedLedgers } from '@algosigner/common/types/ledgers'; +import { getBaseSupportedNetworks } from '@algosigner/common/types/network'; import { logging, LogLevel } from '@algosigner/common/logging'; import TxAcfg from 'components/SignTransaction/TxAcfg'; import TxPay from 'components/SignTransaction/TxPay'; @@ -28,22 +28,22 @@ const LedgerHardwareSign: FunctionalComponent = () => { const [account, setAccount] = useState(''); const [txResponseHeader, setTxResponseHeader] = useState(''); const [txResponseDetail, setTxResponseDetail] = useState(''); - const [ledger, setLedger] = useState(''); + const [network, setNetwork] = useState(''); const [sessionTxnObj, setSessionTxnObj] = useState({}); const [showTooltip, setShowTooltip] = useState(false); let currentTransaction: number = 0; let totalTxns: number = 0; - const updateAccountName = (ledger: string, from: string): void => { - if (store[ledger] && store[ledger].length) { - for (let i = 0; i < store[ledger].length; i++) { - const lookupAddress = store[ledger][i].address; - const lookupName = store[ledger][i].name; + const updateAccountName = (network: string, from: string): void => { + if (store[network] && store[network].length) { + for (let i = 0; i < store[network].length; i++) { + const lookupAddress = store[network][i].address; + const lookupName = store[network][i].name; if (lookupAddress === from) setAccount(lookupName); } } else { setTimeout(() => { - updateAccountName(ledger, from); + updateAccountName(network, from); }, 100); } }; @@ -63,12 +63,12 @@ const LedgerHardwareSign: FunctionalComponent = () => { setSessionTxnObj(returnedSessionObj); setTxn(txToSign); - getBaseSupportedLedgers().forEach((l) => { - if (txToSign.transaction?.genesisID === l['genesisID']) { - const fetchedLedger = l['name']; - setLedger(fetchedLedger); - store.setLedger(fetchedLedger); - updateAccountName(fetchedLedger, txToSign.transaction?.from); + getBaseSupportedNetworks().forEach((n) => { + if (txToSign.transaction?.genesisID === n['genesisID']) { + const matchingNetwork = n['name']; + setNetwork(matchingNetwork); + store.setLedger(matchingNetwork); + updateAccountName(matchingNetwork, txToSign.transaction?.from); } }); } @@ -113,7 +113,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { setSessionTxnObj(message.body.params); setTxn(txToSign); - updateAccountName(ledger, txToSign.transaction?.from); + updateAccountName(network, txToSign.transaction?.from); setShowTooltip(true); } else if (response) { setTxResponseHeader('Transaction(s) signed. Result sent to origin tab.'); @@ -189,7 +189,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { vo=${txn.validityObject} estFee=${txn.estimatedFee} account=${account} - ledger=${ledger} + network=${network} /> `} ${txn.transaction.type === 'keyreg' && @@ -199,7 +199,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { vo=${txn.validityObject} estFee=${txn.estimatedFee} account=${account} - ledger=${ledger} + network=${network} /> `} ${txn.transaction.type === 'acfg' && @@ -210,7 +210,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { dt=${txn.txDerivedTypeText} estFee=${txn.estimatedFee} account=${account} - ledger=${ledger} + network=${network} /> `} ${txn.transaction.type === 'axfer' && @@ -223,7 +223,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { da=${txn.displayAmount} un=${txn.unitName} account=${account} - ledger=${ledger} + network=${network} /> `} ${txn.transaction.type === 'afrz' && @@ -233,7 +233,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { vo=${txn.validityObject} estFee=${txn.estimatedFee} account=${account} - ledger=${ledger} + network=${network} /> `} ${txn.transaction.type === 'appl' && @@ -243,7 +243,7 @@ const LedgerHardwareSign: FunctionalComponent = () => { vo=${txn.validityObject} estFee=${txn.estimatedFee} account=${account} - ledger=${ledger} + network=${network} /> `}
diff --git a/packages/ui/src/components/LedgerNetworksConfiguration.ts b/packages/ui/src/components/LedgerNetworksConfiguration.ts index 95ee8d78..62d69b9b 100644 --- a/packages/ui/src/components/LedgerNetworksConfiguration.ts +++ b/packages/ui/src/components/LedgerNetworksConfiguration.ts @@ -3,7 +3,7 @@ import { html } from 'htm/preact'; import { useState, useContext } from 'preact/hooks'; import { useObserver } from 'mobx-react-lite'; import { StoreContext } from 'services/StoreContext'; -import { LedgerTemplate } from '@algosigner/common/types/ledgers'; +import { NetworkTemplate } from '@algosigner/common/types/network'; import LedgerNetworkModify from './LedgerNetworkModify'; import { route } from 'preact-router'; @@ -20,7 +20,7 @@ const LedgerNetworksConfiguration: FunctionalComponent = (props: any) => { } }); - const setActiveNetwork = (ledger?: LedgerTemplate) => { + const setActiveNetwork = (ledger?: NetworkTemplate) => { if (ledger) { setActiveLedgerTemplate({ ...ledger }); } else { diff --git a/packages/ui/src/components/MainHeader.ts b/packages/ui/src/components/MainHeader.ts index 4d75970f..789d69ed 100644 --- a/packages/ui/src/components/MainHeader.ts +++ b/packages/ui/src/components/MainHeader.ts @@ -2,7 +2,7 @@ import { FunctionalComponent } from 'preact'; import { html } from 'htm/preact'; import HeaderComponent from './HeaderComponent'; -import LedgerSelect from './LedgerSelect'; +import NetworkSelect from './NetworkSelect'; import OpenContacts from './OpenContacts'; import SettingsMenu from './SettingsMenu'; import Logo from './Logo'; @@ -11,7 +11,7 @@ const MainHeader: FunctionalComponent = () => { return html` <${HeaderComponent}> <${Logo} /> - <${LedgerSelect} /> + <${NetworkSelect} /> <${OpenContacts} /> <${SettingsMenu} /> diff --git a/packages/ui/src/components/LedgerSelect.ts b/packages/ui/src/components/NetworkSelect.ts similarity index 71% rename from packages/ui/src/components/LedgerSelect.ts rename to packages/ui/src/components/NetworkSelect.ts index 03558b81..81443fc0 100644 --- a/packages/ui/src/components/LedgerSelect.ts +++ b/packages/ui/src/components/NetworkSelect.ts @@ -7,14 +7,14 @@ import { JsonRpcMethod } from '@algosigner/common/messaging/types'; import { StoreContext } from 'services/StoreContext'; import { sendMessage } from 'services/Messaging'; -const LedgerSelect: FunctionalComponent = () => { +const NetworkSelect: FunctionalComponent = () => { const store: any = useContext(StoreContext); const [active, setActive] = useState(false); - let sessionLedgers; - store.getAvailableLedgers((availableLedgers) => { - if (!availableLedgers.error) { - sessionLedgers = availableLedgers; + let sessionNetworks; + store.getAvailableLedgers((availableNetworks) => { + if (!availableNetworks.error) { + sessionNetworks = availableNetworks; } }); @@ -25,12 +25,12 @@ const LedgerSelect: FunctionalComponent = () => { setActive(!active); }; - const setLedger = (ledger) => { + const setLedger = (network) => { const params = { - ledger: ledger, + ledger: network, }; - sendMessage(JsonRpcMethod.ChangeLedger, params, function () { - store.setLedger(ledger); + sendMessage(JsonRpcMethod.ChangeNetwork, params, function () { + store.setLedger(network); flip(); route('/wallet'); }); @@ -57,16 +57,16 @@ const LedgerSelect: FunctionalComponent = () => {