From 22892576a276af35f6402c4bbf049c6d0bf235d5 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Fri, 16 Aug 2024 15:16:38 -0300 Subject: [PATCH 001/104] Add new sdk methods to flyover service --- jest.config.js | 1 + src/common/services/FlyoverService.ts | 56 +++++++++++++++++++ .../common/services/FlyoverService.spec.ts | 29 ++++++++-- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/jest.config.js b/jest.config.js index 86f530e0a..90fc2729c 100644 --- a/jest.config.js +++ b/jest.config.js @@ -13,4 +13,5 @@ module.exports = { coverageProvider: 'v8', transformIgnorePatterns: ['node_modules/(?!axios)/'], setupFilesAfterEnv: ['/setup-jest.js'], + silent: true, }; diff --git a/src/common/services/FlyoverService.ts b/src/common/services/FlyoverService.ts index 8f50b206e..c22f811aa 100644 --- a/src/common/services/FlyoverService.ts +++ b/src/common/services/FlyoverService.ts @@ -3,6 +3,7 @@ import { AcceptedPegoutQuote, Flyover, LiquidityProvider, PegoutQuote, Quote, AcceptedQuote, + FlyoverUtils, } from '@rsksmart/flyover-sdk'; import * as constants from '@/common/store/constants'; import { @@ -443,4 +444,59 @@ export default class FlyoverService { } }); } + + public getAvailableLiquidity(): Promise<{peginLiquidity: WeiBig, pegoutLiquidity: SatoshiBig}> { + return new Promise((resolve, reject) => { + this.flyover?.getAvailableLiquidity() + .then(({ peginLiquidityAmount, pegoutLiquidityAmount }) => { + const peginLiquidity = new WeiBig(peginLiquidityAmount, 'wei'); + const pegoutLiquidity = new SatoshiBig(pegoutLiquidityAmount, 'satoshi'); + resolve({ peginLiquidity, pegoutLiquidity }); + }) + .catch((error) => { + reject(new ServiceError( + 'FlyoverService', + 'getAvailableLiquidity', + 'There was an error getting the available liquidity from the Flyover server', + error.message, + )); + }); + }); + } + + public getPeginStatus(quoteHash: string) { + return new Promise((resolve, reject) => { + this.flyover?.getPeginStatus(quoteHash) + .then((detailedStatus) => { + const status = FlyoverUtils.getSimpleQuoteStatus(detailedStatus.status.state); + resolve(status); + }) + .catch((error) => { + reject(new ServiceError( + 'FlyoverService', + 'getPeginStatus', + 'There was an error getting the status of the peg-in transaction from the Flyover server', + error.message, + )); + }); + }); + } + + public getPegoutStatus(quoteHash: string) { + return new Promise((resolve, reject) => { + this.flyover?.getPegoutStatus(quoteHash) + .then((detailedStatus) => { + const status = FlyoverUtils.getSimpleQuoteStatus(detailedStatus.status.state); + resolve(status); + }) + .catch((error) => { + reject(new ServiceError( + 'FlyoverService', + 'getPegoutStatus', + 'There was an error getting the status of the peg-out transaction from the Flyover server', + error.message, + )); + }); + }); + } } diff --git a/tests/unit/common/services/FlyoverService.spec.ts b/tests/unit/common/services/FlyoverService.spec.ts index d4329493d..d04ad3444 100644 --- a/tests/unit/common/services/FlyoverService.spec.ts +++ b/tests/unit/common/services/FlyoverService.spec.ts @@ -1,9 +1,10 @@ import FlyoverService from '@/common/services/FlyoverService'; import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; import { SatoshiBig, WeiBig } from '@/common/types'; -import { Flyover } from '@rsksmart/flyover-sdk'; +import { Flyover, FlyoverUtils } from '@rsksmart/flyover-sdk'; import * as constants from '@/common/store/constants'; import sinon from 'sinon'; +import { ServiceError } from '@/common/utils'; describe('FlyoverService', () => { let flyoverService: FlyoverService; @@ -128,8 +129,6 @@ describe('FlyoverService', () => { expect(provider).toHaveProperty('id'); expect(provider).toHaveProperty('name'); }); - - test.todo('should handle errors when fetching providers'); }); describe('getPegoutQuotes', () => { @@ -324,6 +323,7 @@ describe('FlyoverService', () => { const btcRefundAddress = 'n2y5V6LYszsrsxkMdMypL98YQxtBoLCXdc'; const btcRecipientAddress = 'n2y5V6LYszsrsxkMdMypL98YQxtBoLCXdc'; const valueToTransfer = new WeiBig('0.005', 'rbtc'); + const expectedTotalAmount = 5255689215476000n; const quotes = await flyoverService.getPegoutQuotes( rskRefundAddress, @@ -341,7 +341,28 @@ describe('FlyoverService', () => { expect(acceptedQuote).toBe('txHash'); expect(spyAcceptPegoutQuote).toHaveBeenCalled(); expect(spyIsValidAcceptedQuote).toHaveBeenCalled(); - expect(spyDepositPegout).toHaveBeenCalledWith(flyoverService['pegoutQuotes'][0], 'signature', 5255689215476000n); + expect(spyDepositPegout).toHaveBeenCalledWith(flyoverService['pegoutQuotes'][0], 'signature', expectedTotalAmount); + expect(FlyoverUtils.getQuoteTotal(flyoverService['pegoutQuotes'][0])).toEqual(expectedTotalAmount); + }); + }); + + describe('getAvailableLiquidity', () => { + it('should return the available liquidity', async () => { + const stubedInstance = sinon.createStubInstance(Flyover); + flyoverService.flyover = stubedInstance; + stubedInstance.getAvailableLiquidity.resolves({ + peginLiquidityAmount: 1000000000000000000n, + pegoutLiquidityAmount: 1000000000000000000n, + }); + const liquidity = await flyoverService.getAvailableLiquidity(); + expect(liquidity.peginLiquidity).toBeInstanceOf(WeiBig); + expect(liquidity.pegoutLiquidity).toBeInstanceOf(SatoshiBig); + }); + it('should throw an error if the available liquidity is not found', async () => { + const stubedInstance = sinon.createStubInstance(Flyover); + flyoverService.flyover = stubedInstance; + stubedInstance.getAvailableLiquidity.rejects(); + await expect(flyoverService.getAvailableLiquidity()).rejects.toThrowError(ServiceError); }); }); }); From 998f5dd589a4048fdf87a283a4adbfd894ac8229 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Tue, 10 Sep 2024 15:10:03 -0300 Subject: [PATCH 002/104] Set version to 2.3.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e9e122955..ad172e37b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "PowPeg", - "version": "2.2.1", + "version": "2.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "PowPeg", - "version": "2.2.1", + "version": "2.3.0", "dependencies": { "@leather.io/rpc": "^2.0.2", "@ledgerhq/devices": "6.27.1", diff --git a/package.json b/package.json index f251cc9d5..81380f672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "PowPeg", - "version": "2.2.1", + "version": "2.3.0", "private": true, "scripts": { "serve": "vue-cli-service serve", From 3dd70f74a235d662dfa31d650e3062b7859fdf05 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Mon, 9 Sep 2024 16:24:09 -0300 Subject: [PATCH 003/104] Save quote hash --- src/common/types/TxInfo.ts | 1 + src/pegin/components/create/ConfirmTx.vue | 1 + src/pegout/components/FlyoverPegout.vue | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/common/types/TxInfo.ts b/src/common/types/TxInfo.ts index 18dc876a5..8ac804235 100644 --- a/src/common/types/TxInfo.ts +++ b/src/common/types/TxInfo.ts @@ -10,4 +10,5 @@ export interface TxInfo { btcEstimatedFee?: number; provider?: string; details?: Record; + quoteHash?: string; } diff --git a/src/pegin/components/create/ConfirmTx.vue b/src/pegin/components/create/ConfirmTx.vue index d01022adb..85c4db015 100644 --- a/src/pegin/components/create/ConfirmTx.vue +++ b/src/pegin/components/create/ConfirmTx.vue @@ -171,6 +171,7 @@ export default defineComponent({ recipientAddress: recipientAddress.value, blocksToCompleteTransaction: selectedQuote.value.quote.confirmations, }, + quoteHash: selectedQuote.value.quoteHash, })); const nativeProps = computed(() => ({ diff --git a/src/pegout/components/FlyoverPegout.vue b/src/pegout/components/FlyoverPegout.vue index 501c0d95b..6bcf00ebb 100644 --- a/src/pegout/components/FlyoverPegout.vue +++ b/src/pegout/components/FlyoverPegout.vue @@ -110,7 +110,7 @@ import { import { mdiArrowLeft, mdiArrowRight } from '@mdi/js'; import { FlyoverPegoutState, ObjectDifference, PegOutTxState, QuotePegOut2WP, - SatoshiBig, TxStatusType, WeiBig, + SatoshiBig, TxInfo, TxStatusType, WeiBig, } from '@/common/types'; import { appendRecaptcha, Machine, ServiceError, validateAddress, @@ -280,7 +280,7 @@ export default defineComponent({ .toRBTCTrimmedString(); } - const registerFlyover = computed(() => ({ + const registerFlyover = computed(() => ({ sessionId: '', txHash: flyoverPegoutState.value.txHash as string, type: TxStatusType.PEGOUT.toLowerCase(), @@ -294,6 +294,7 @@ export default defineComponent({ recipientAddress: flyoverPegoutState.value.btcRecipientAddress, blocksToCompleteTransaction: selectedQuote.value.quote.depositConfirmations, }, + quoteHash: selectedQuote.value.quoteHash, })); const registerPegout = computed(() => ({ From 4905ed42cfc3040b78ab3007ea69adf381c285b9 Mon Sep 17 00:00:00 2001 From: ronaldsg Date: Fri, 20 Sep 2024 13:46:51 -0500 Subject: [PATCH 004/104] Add xverse wallet support for Pegin --- jest.config.js | 4 + package-lock.json | 101 +++++++++++- package.json | 1 + src/assets/wallet-icons/xverse-white.png | Bin 0 -> 963 bytes src/assets/wallet-icons/xverse.png | Bin 0 -> 944 bytes .../exchange/SelectBitcoinWallet.vue | 3 + src/common/services/XverseService.ts | 144 ++++++++++++++++++ src/common/services/index.ts | 1 + src/common/store/constants.ts | 1 + src/common/types/Common.ts | 6 + src/common/types/pegInTx.ts | 2 +- src/common/utils/btcAddressUtils.ts | 14 ++ src/common/walletConf.json | 12 ++ src/pegin/components/create/SendBitcoin.vue | 5 + .../middleware/TxBuilder/XverseTxBuilder.ts | 78 ++++++++++ src/pegin/store/PeginTx/actions.ts | 5 +- src/pegin/store/PeginTx/getters.ts | 9 ++ tests/unit/common/services/XverseService.ts | 140 +++++++++++++++++ 18 files changed, 520 insertions(+), 6 deletions(-) create mode 100644 src/assets/wallet-icons/xverse-white.png create mode 100644 src/assets/wallet-icons/xverse.png create mode 100644 src/common/services/XverseService.ts create mode 100644 src/pegin/middleware/TxBuilder/XverseTxBuilder.ts create mode 100644 tests/unit/common/services/XverseService.ts diff --git a/jest.config.js b/jest.config.js index 90fc2729c..10a02b966 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,8 @@ module.exports = { + transform: { + '^.+\\.vue$': '@vue/vue3-jest', + '^.+\\.(mts|mjs|jsx|ts|tsx)$': 'ts-jest', + }, preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', collectCoverage: true, collectCoverageFrom: ['src/(common|pegin)/(providers|services|utils)/*.ts'], diff --git a/package-lock.json b/package-lock.json index ad172e37b..b534f4c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "moment": "^2.29.4", "os-browserify": "^0.3.0", "process": "^0.11.10", + "sats-connect": "^2.8.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "text-encoding": "^0.7.0", @@ -7102,6 +7103,39 @@ "version": "1.0.1", "license": "ISC" }, + "node_modules/@sats-connect/core": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.2.2.tgz", + "integrity": "sha512-nl3zPnV1UBllYAniDfhM/oSFGQ2qy4cCg1YwxJZ+RQMwlTMrVh2f3lJ//dIIo9RgQPrtHpwrAaaWW0VpfqDQbg==", + "dependencies": { + "axios": "1.7.4", + "bitcoin-address-validation": "2.2.3", + "buffer": "6.0.3", + "jsontokens": "4.0.1", + "lodash.omit": "4.5.0", + "valibot": "0.33.2" + } + }, + "node_modules/@sats-connect/core/node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/@sats-connect/core/node_modules/bitcoin-address-validation": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz", + "integrity": "sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg==", + "dependencies": { + "base58-js": "^1.0.0", + "bech32": "^2.0.0", + "sha256-uint8array": "^0.10.3" + } + }, + "node_modules/@sats-connect/ui": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@sats-connect/ui/-/ui-0.0.6.tgz", + "integrity": "sha512-H3bFFhr9CcY1oNosNi/QJszmMHSht4U19bUWfM3vzayAKgV4ebY6iUnRK5g3p2rVLLWVzlpaw1J9m+7JWwyBfA==" + }, "node_modules/@scure/base": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz", @@ -10768,11 +10802,11 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.3.tgz", - "integrity": "sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { - "follow-redirects": "^1.15.0", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } @@ -11153,6 +11187,14 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/base58-js": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/base58-js/-/base58-js-1.0.5.tgz", + "integrity": "sha512-LkkAPP8Zu+c0SVNRTRVDyMfKVORThX+rCViget00xdgLRrKkClCTz1T7cIrpr69ShwV5XJuuoZvMvJ43yURwkA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/base64-js": { "version": "1.5.1", "funding": [ @@ -22502,6 +22544,11 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.omit": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.5.0.tgz", + "integrity": "sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==" + }, "node_modules/lodash.truncate": { "version": "4.4.2", "dev": true, @@ -26511,6 +26558,42 @@ } } }, + "node_modules/sats-connect": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-2.8.0.tgz", + "integrity": "sha512-eYdpPoAXn6ud1hMZnQGowO1F0f9fS3jmE5Hq1F3VxXUbAvT2YmA72PBtG6QN/cdMuFZ5x1ce6I/fl270WSXqjw==", + "dependencies": { + "@sats-connect/core": "0.2.2", + "@sats-connect/make-default-provider-config": "0.0.5", + "@sats-connect/ui": "0.0.6" + } + }, + "node_modules/sats-connect/node_modules/@sats-connect/make-default-provider-config": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@sats-connect/make-default-provider-config/-/make-default-provider-config-0.0.5.tgz", + "integrity": "sha512-b/v4IeDEde5DqFOdMbMmf3B0t/lxlKnY04f3YIUWe1khOg3S6VdcK9Mqva+WUOsJHBTIA5b4hK7CqfMjx1Ic+w==", + "dependencies": { + "@sats-connect/ui": "0.0.6", + "bowser": "2.11.0" + }, + "peerDependencies": { + "@sats-connect/core": "*", + "typescript": "5.4.4" + } + }, + "node_modules/sats-connect/node_modules/typescript": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", + "integrity": "sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", @@ -26844,6 +26927,11 @@ "sha.js": "bin.js" } }, + "node_modules/sha256-uint8array": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.7.tgz", + "integrity": "sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ==" + }, "node_modules/shallow-clone": { "version": "3.0.1", "license": "MIT", @@ -29246,6 +29334,11 @@ "node": ">= 8" } }, + "node_modules/valibot": { + "version": "0.33.2", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.33.2.tgz", + "integrity": "sha512-ZpFWuI+bs5+PP66q4zVFn4e4t/s5jmMw5iPBZmGUoi8iQqXyU9YY/BLCAyk62Z/bNS8qdUNBEyx52952qdqW3w==" + }, "node_modules/valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", diff --git a/package.json b/package.json index 81380f672..31f7efe1b 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "moment": "^2.29.4", "os-browserify": "^0.3.0", "process": "^0.11.10", + "sats-connect": "^2.8.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "text-encoding": "^0.7.0", diff --git a/src/assets/wallet-icons/xverse-white.png b/src/assets/wallet-icons/xverse-white.png new file mode 100644 index 0000000000000000000000000000000000000000..76b3e19ab399efeca2e84400dc9f883b7cab3884 GIT binary patch literal 963 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDAAG{;hE;^%b*2hb1<+n z3NbJPS&Tr)z$nE4G7ZRL@M4sPvx68lplX;H7}_%#SfFa6fHVkr05M1pgl1mAh%j*h z6I`|A0%imoq;2)dU$H=nv%n*=n1O*?7=#%aX3dcRniiQE5>XQ2>tmIipR1RclAn~S zSCLx)(#2p?VFhI7rj{fsROII56h?X&sHg;q@=(~U%$M(T(8_%FTW^V-_X+1Qs2Nx-^fT8 zs6w~6GOr}DLN~8i8Da>`9GBGM%@E9OzK% zs1tVhf2a9G@)50$D)ENntP{_7uy)*froCwT(tM%MYv)S5r> Nnx3wHF6*2UngC^jDYgIr literal 0 HcmV?d00001 diff --git a/src/assets/wallet-icons/xverse.png b/src/assets/wallet-icons/xverse.png new file mode 100644 index 0000000000000000000000000000000000000000..c65e0b6640571aa159986b58ed2dc35d2cb8e4ad GIT binary patch literal 944 zcmeAS@N?(olHy`uVBq!ia0vp^k|4~%1|*NXY)uAIEa{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDAAG{;hE;^%b*2hb1<+n z3NbJPS&Tr)z$nE4G7ZRL@M4sPvx68lplX;H7}_%#SfFa6fHVkr05M1pgl1mAh%j*h z6I`|A0%imoq;2)dU$H=nv%n*=n1O*?7=#%aX3dcRniiQE5>XQ2>tmIipR1RclAn~S zSCLx)(#2p?VFhI7rj{fsROII56h?X&sHg;q@=(~U%$M(T(8_%FTW^V-_X+1Qs2Nx-^fT8 zs6w~6GOr}DLN~8i8Da>`9GBGMt<@S1PD>ORIVTITM=-E@{7_X$6q>;B zp7+#K{oGl7PxWGN?U|lm;_jtst7ZAvXM*y^_8I$b?Rllxs;t12$IFmzsu&o+f1&k@ zgP;S)qsBeji}cfjHlJ+Y`kZI3Lh+n{D(c)`fc$*FOAKBig|r^>#s=ElU*h34H;kzf2sB+Zc8%5IhjyRlB-A z+?*+ZL89wS{##pn1+jSs-_>5^XqfGMZks;KhOzq3dKt|%EsxebPv5WoNJ0Nx#;yf( spYwfFd!aY4jGN=R{|U7W?GElLCbz~U{&f?=Z-LUGr>mdKI;Vst02W6VQvd(} literal 0 HcmV?d00001 diff --git a/src/common/components/exchange/SelectBitcoinWallet.vue b/src/common/components/exchange/SelectBitcoinWallet.vue index 4a44919ae..80c595d14 100644 --- a/src/common/components/exchange/SelectBitcoinWallet.vue +++ b/src/common/components/exchange/SelectBitcoinWallet.vue @@ -89,6 +89,9 @@ export default { case constants.WALLET_NAMES.LEATHER.long_name: wallet = constants.WALLET_NAMES.LEATHER.short_name; break; + case constants.WALLET_NAMES.XVERSE.long_name: + wallet = constants.WALLET_NAMES.XVERSE.short_name; + break; default: wallet = ''; break; diff --git a/src/common/services/XverseService.ts b/src/common/services/XverseService.ts new file mode 100644 index 000000000..9725c7edd --- /dev/null +++ b/src/common/services/XverseService.ts @@ -0,0 +1,144 @@ +/* eslint-disable class-methods-use-this */ +import Wallet, { AddressPurpose, BitcoinNetworkType } from 'sats-connect'; +import * as bitcoin from 'bitcoinjs-lib'; +import { WalletService } from '@/common/services/index'; +import * as constants from '@/common/store/constants'; +import { + WalletAddress, Tx, SignedTx, BtcAccount, Step, + XverseTx, +} from '../types'; + +export default class XverseService extends WalletService { + satsBtcNetwork: BitcoinNetworkType; + + constructor() { + super(); + switch (this.network) { + case constants.BTC_NETWORK_MAINNET: + this.satsBtcNetwork = BitcoinNetworkType.Mainnet; + break; + default: + this.satsBtcNetwork = BitcoinNetworkType.Testnet; + break; + } + } + + getAccountAddresses(): Promise { + return new Promise((resolve, reject) => { + const walletAddresses: WalletAddress[] = []; + const payload = { + purposes: ['payment'] as AddressPurpose[], + message: 'Welcome to the 2wp-app, please select your Bitcoin account to start.', + network: { + type: this.satsBtcNetwork, + }, + }; + Wallet.request('getAddresses', payload) + .then((response) => { + if (response.status === 'error') { + reject(new Error(response.error.message)); + } else { + response.result.addresses + .forEach((addr: { address: string; publicKey: string; }) => { + walletAddresses.push({ + address: addr.address, + publicKey: addr.publicKey, + derivationPath: '', + }); + }); + } + resolve(walletAddresses); + }) + .catch(reject); + }); + } + + sign(tx: Tx): Promise { + const xverseTx = tx as XverseTx; + return new Promise((resolve, reject) => { + const signInputs: Record = {}; + xverseTx.inputs.forEach((input: { address: string; idx: number; }, inputIdx: number) => { + if (signInputs[input.address]) { + signInputs[input.address].push(inputIdx); + } else { + signInputs[input.address] = [inputIdx]; + } + }); + const signPsbtOptions = { + psbt: xverseTx.base64UnsignedPsbt, + signInputs, + broadcast: false, + }; + Wallet.request('signPsbt', signPsbtOptions) + .then((response) => { + if (response.status === 'error') { + reject(new Error(response.error.message)); + } else { + const signedPsbt = bitcoin.Psbt.fromBase64(response.result.psbt as string); + if (!signedPsbt.validateSignaturesOfAllInputs()) { + reject(new Error('Invalid signature provided')); + } else { + resolve({ + signedTx: signedPsbt.finalizeAllInputs().extractTransaction().toHex(), + }); + } + } + }) + .catch(() => reject(new Error('Invalid psbt provided'))); + }); + } + + isConnected(): Promise { + return Promise.resolve(true); + } + + reconnect(): Promise { + return new Promise((resolve, reject) => { + this.getAccountAddresses() + .then(() => resolve()) + .catch(reject); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getXpub(accountType: BtcAccount, accountNumber: number): Promise { + throw new Error('Method not supported.'); + } + + areEnoughUnusedAddresses(): boolean { + return this.addressesToFetch.segwit.lastIndex >= 1; + } + + availableAccounts(): BtcAccount[] { + return [constants.BITCOIN_SEGWIT_ADDRESS]; + } + + name(): Record<'formal_name' | 'short_name' | 'long_name', string> { + return constants.WALLET_NAMES.XVERSE; + } + + confirmationSteps(): Step[] { + return [ + { + title: 'Transaction information', + subtitle: '', + outputsToshow: { + opReturn: { + value: false, + amount: true, + }, + change: { + address: true, + amount: true, + }, + federation: { + address: true, + amount: true, + }, + }, + fullAmount: false, + fee: true, + }, + ]; + } +} diff --git a/src/common/services/index.ts b/src/common/services/index.ts index 17c102547..be5b73a3a 100644 --- a/src/common/services/index.ts +++ b/src/common/services/index.ts @@ -4,3 +4,4 @@ export { default as TrezorService } from './TrezorService'; export { default as LedgerService } from './LedgerService'; export { default as LeatherService } from './LeatherService'; export { default as FlyoverService } from './FlyoverService'; +export { default as XverseService } from './XverseService'; diff --git a/src/common/store/constants.ts b/src/common/store/constants.ts index a7796c314..2b02be99d 100644 --- a/src/common/store/constants.ts +++ b/src/common/store/constants.ts @@ -3,6 +3,7 @@ export const WALLET_NAMES = { TREZOR: { formal_name: 'Trezor', short_name: 'trezor', long_name: 'WALLET_TREZOR' }, METAMASK: { formal_name: 'Metamask', short_name: 'metamask', long_name: 'WALLET_METAMASK' }, LEATHER: { formal_name: 'Leather', short_name: 'leather', long_name: 'WALLET_LEATHER' }, + XVERSE: { formal_name: 'XVerse', short_name: 'xverse', long_name: 'WALLET_XVERSE' }, } as const; export const OPERATION_TYPE = 'OPERATION_TYPE'; diff --git a/src/common/types/Common.ts b/src/common/types/Common.ts index fa5c5b291..182b7871d 100644 --- a/src/common/types/Common.ts +++ b/src/common/types/Common.ts @@ -109,6 +109,7 @@ export interface PsbtExtendedInput { value: number; script: Buffer; }; + redeemScript?: Buffer; } export interface NormalizedSummary { @@ -140,3 +141,8 @@ export enum AppLocale { LOCALE_EN = 'en', LOCALE_ES = 'es', } + +export interface XverseTx extends Tx { + base64UnsignedPsbt: string; + inputs: Array<{idx: number; address: string}>; +} diff --git a/src/common/types/pegInTx.ts b/src/common/types/pegInTx.ts index 601ecc39e..a35980eec 100644 --- a/src/common/types/pegInTx.ts +++ b/src/common/types/pegInTx.ts @@ -7,7 +7,7 @@ export type BtcAccount = 'BITCOIN_LEGACY_ADDRESS' | 'BITCOIN_SEGWIT_ADDRESS' | 'BITCOIN_NATIVE_SEGWIT_ADDRESS'; -export type BtcWallet = 'WALLET_LEDGER' | 'WALLET_TREZOR' | 'WALLET_LEATHER'; +export type BtcWallet = 'WALLET_LEDGER' | 'WALLET_TREZOR' | 'WALLET_LEATHER' | 'WALLET_XVERSE'; export type MiningSpeedFee = 'BITCOIN_SLOW_FEE_LEVEL' | 'BITCOIN_AVERAGE_FEE_LEVEL' | diff --git a/src/common/utils/btcAddressUtils.ts b/src/common/utils/btcAddressUtils.ts index 73ea16682..f013f8dce 100644 --- a/src/common/utils/btcAddressUtils.ts +++ b/src/common/utils/btcAddressUtils.ts @@ -52,3 +52,17 @@ export function validateAddress(address: string): {valid: boolean; addressType: } return { valid, addressType }; } + +function compressPublicKey(pubKey: string) { + const { publicKey } = bitcoin.ECPair.fromPublicKey(Buffer.from(pubKey, 'hex')); + return publicKey.toString('hex'); +} + +export function getP2SHRedeemScript(publicKey: string, network: bitcoin.Network) { + const pubkey = compressPublicKey(publicKey); + const pair = bitcoin.ECPair.fromPublicKey(Buffer.from(pubkey, 'hex')); + const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: pair.publicKey, network }); + const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network }); + const redeem = p2sh.redeem?.output; + return redeem; +} diff --git a/src/common/walletConf.json b/src/common/walletConf.json index 0f2d66c06..390f6b0d9 100644 --- a/src/common/walletConf.json +++ b/src/common/walletConf.json @@ -35,6 +35,18 @@ "btnClass": "btn-leather", "kind": "Software Wallet", "installation": "https://leather.io/install-extension" + }, + { + "name": "Xverse", + "icon": "wallet-icons/xverse.png", + "iconWhite": "wallet-icons/xverse-white.png", + "constant": "WALLET_XVERSE", + "pegin": true, + "pegout": false, + "hover": false, + "btnClass": "btn-xverse", + "kind": "Software Wallet", + "installation": "https://www.xverse.app/download" } ] } diff --git a/src/pegin/components/create/SendBitcoin.vue b/src/pegin/components/create/SendBitcoin.vue index 2a41262fa..31a0531e5 100644 --- a/src/pegin/components/create/SendBitcoin.vue +++ b/src/pegin/components/create/SendBitcoin.vue @@ -51,6 +51,7 @@ import TxErrorDialog from '@/common/components/exchange/TxErrorDialog.vue'; import { TrezorError } from '@/common/types/exception/TrezorError'; import LeatherTxBuilder from '@/pegin/middleware/TxBuilder/LeatherTxBuilder'; import PeginTxService from '@/pegin/services/PeginTxService'; +import XverseTxBuilder from '@/pegin/middleware/TxBuilder/XverseTxBuilder'; export default defineComponent({ name: 'SendBitcoin', @@ -193,6 +194,10 @@ export default defineComponent({ txBuilder.value = new LeatherTxBuilder(); currentWallet.value = constants.WALLET_NAMES.LEATHER.short_name; break; + case constants.WALLET_NAMES.XVERSE.long_name: + txBuilder.value = new XverseTxBuilder(); + currentWallet.value = constants.WALLET_NAMES.XVERSE.short_name; + break; default: txBuilder.value = new TrezorTxBuilder(); break; diff --git a/src/pegin/middleware/TxBuilder/XverseTxBuilder.ts b/src/pegin/middleware/TxBuilder/XverseTxBuilder.ts new file mode 100644 index 000000000..e14ded5fe --- /dev/null +++ b/src/pegin/middleware/TxBuilder/XverseTxBuilder.ts @@ -0,0 +1,78 @@ +import { ApiService } from '@/common/services'; +import store from '@/common/store'; +import { + NormalizedInput, NormalizedTx, PsbtExtendedInput, + XverseTx, +} from '@/common/types'; +import * as bitcoin from 'bitcoinjs-lib'; +import * as constants from '@/common/store/constants'; +import { getP2SHRedeemScript } from '@/common/utils'; +import TxBuilder from './TxBuilder'; + +export default class XverseTxBuilder extends TxBuilder { + buildTx(normalizedTx: NormalizedTx): Promise { + return new Promise((resolve, reject) => { + const psbt = new bitcoin.Psbt({ network: this.network }); + this.getExtendedInputs(normalizedTx.inputs) + .then((extendedInputs) => { + psbt.addInputs(extendedInputs); + normalizedTx.outputs.forEach((normalizedOutput) => { + if (normalizedOutput.op_return_data) { + const buffer = Buffer.from(normalizedOutput.op_return_data, 'hex'); + const script: bitcoin.Payment = bitcoin.payments.embed({ data: [buffer] }); + if (script.output) { + psbt.addOutput({ + script: script.output, + value: 0, + }); + } + } else if (normalizedOutput.address) { + psbt.addOutput({ + address: normalizedOutput.address, + value: Number(normalizedOutput.amount), + }); + } + }); + const inputs = normalizedTx.inputs + .map((input) => ({ + address: input.address, + idx: input.prev_index, + })); + resolve({ + coin: this.coin, + inputs, + outputs: normalizedTx.outputs, + base64UnsignedPsbt: psbt.toBase64(), + }); + }) + .catch(reject); + }); + } + + private getExtendedInputs(normalizedInputs: Array) + :Promise> { + return new Promise>((resolve, reject) => { + const psbtExtendedInputs: Array = []; + const hexUtxoPromises = normalizedInputs + .map((input) => ApiService.getTxHex(input.prev_hash)); + Promise.all(hexUtxoPromises) + .then((hexUtxos) => { + normalizedInputs.forEach((normalizedInput, idx) => { + const utxo = bitcoin.Transaction.fromHex(hexUtxos[idx]); + const pubKey = store.getters[`pegInTx/${constants.PEGIN_TX_GET_ADDRESS_PUBLIC_KEY}`](normalizedInput.address); + psbtExtendedInputs.push({ + hash: normalizedInput.prev_hash, + index: normalizedInput.prev_index, + witnessUtxo: { + value: utxo.outs[normalizedInput.prev_index].value, + script: utxo.outs[normalizedInput.prev_index].script, + }, + redeemScript: getP2SHRedeemScript(pubKey, this.network), + }); + }); + resolve(psbtExtendedInputs); + }) + .catch(reject); + }); + } +} diff --git a/src/pegin/store/PeginTx/actions.ts b/src/pegin/store/PeginTx/actions.ts index be1efdc82..69e2e1813 100644 --- a/src/pegin/store/PeginTx/actions.ts +++ b/src/pegin/store/PeginTx/actions.ts @@ -4,7 +4,7 @@ import * as rskUtils from '@rsksmart/rsk-utils'; import * as constants from '@/common/store/constants'; import { ApiService, LedgerService, - TrezorService, LeatherService, + TrezorService, LeatherService, XverseService, } from '@/common/services'; import SatoshiBig from '@/common/types/SatoshiBig'; import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; @@ -45,6 +45,9 @@ export const actions: ActionTree = { case constants.WALLET_NAMES.LEATHER.long_name: commit(constants.PEGIN_TX_SET_WALLET_SERVICE, new LeatherService()); break; + case constants.WALLET_NAMES.XVERSE.long_name: + commit(constants.PEGIN_TX_SET_WALLET_SERVICE, new XverseService()); + break; default: commit(constants.PEGIN_TX_SET_WALLET_SERVICE, undefined); break; diff --git a/src/pegin/store/PeginTx/getters.ts b/src/pegin/store/PeginTx/getters.ts index e2dd8b723..d46e6080c 100644 --- a/src/pegin/store/PeginTx/getters.ts +++ b/src/pegin/store/PeginTx/getters.ts @@ -17,6 +17,9 @@ export const getters: GetterTree = { case constants.WALLET_NAMES.LEATHER.long_name: { return constants.WALLET_NAMES.LEATHER.formal_name; } + case constants.WALLET_NAMES.XVERSE.long_name: { + return constants.WALLET_NAMES.XVERSE.formal_name; + } default: { return 'wallet'; } @@ -167,6 +170,9 @@ export const getters: GetterTree = { case constants.WALLET_NAMES.LEATHER.long_name: isHdWallet = false; break; + case constants.WALLET_NAMES.XVERSE.long_name: + isHdWallet = false; + break; default: isHdWallet = false; break; @@ -185,6 +191,9 @@ export const getters: GetterTree = { case constants.WALLET_NAMES.LEATHER.long_name: isSfWallet = true; break; + case constants.WALLET_NAMES.XVERSE.long_name: + isSfWallet = true; + break; default: isSfWallet = false; break; diff --git a/tests/unit/common/services/XverseService.ts b/tests/unit/common/services/XverseService.ts new file mode 100644 index 000000000..ac61192e1 --- /dev/null +++ b/tests/unit/common/services/XverseService.ts @@ -0,0 +1,140 @@ +import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; +import * as constants from '@/common/store/constants'; +import sinon from 'sinon'; +import Wallet, { AddressPurpose, AddressType } from 'sats-connect'; +import { WalletService, XverseService } from '@/common/services'; + +const initEnvironment = () => { + const defaultEnvironmentVariables = { + vueAppCoin: constants.BTC_NETWORK_TESTNET, + vueAppManifestAppUrl: '', + vueAppManifestEmail: '', + vueAppWalletAddressPerCall: 5, + vueAppWalletAddressHardStop: 100, + }; + EnvironmentAccessorService.initializeEnvironmentVariables(defaultEnvironmentVariables); +}; + +describe('Xverse Service:', () => { + // let request: sinon.SinonStub<[XverseRequestArgs], Promise>; + // let mockedXverseProvider: sinon.SinonStubbedInstance; + + beforeEach(initEnvironment); + afterEach(() => sinon.restore()); + + it('should create a XverseService instance', () => { + const xverseService = new XverseService(); + expect(xverseService).toBeInstanceOf(XverseService); + expect(xverseService).toBeInstanceOf(WalletService); + }); + + it('should return a single address since that is the current amount supported by xverse', () => { + sinon.stub(Wallet, 'request').resolves({ + status: 'success', + result: { + addresses: [ + { + address: 'testAddress', + publicKey: 'testPublicKey', + purpose: AddressPurpose.Payment, + addressType: AddressType.p2pkh, + }, + ], + }, + }); + const xverseService = new XverseService(); + xverseService.getAccountAddresses().then((addresses) => { + expect(addresses.length).toBe(1); + expect(addresses[0].address).toBe('testAddress'); + expect(addresses[0].publicKey).toBe('testPublicKey'); + }); + }); + + it('should handle the error case when the wallet are not available', () => { + sinon.stub(Wallet, 'request').resolves({ + status: 'error', + error: { + code: 1, + message: 'Wallet not available', + }, + }); + const xverseService = new XverseService(); + expect(xverseService.getAccountAddresses()) + .rejects + .toThrow('Wallet not available'); + }); + + it('should return an error when the psbt are wrong', () => { + sinon.stub(Wallet, 'request').resolves({ + status: 'success', + result: { + psbt: 'cHNidP8BAHECAAAAAW4TCBaK74DxafvrRdWpF32Gg5eVRs1DJX9YHz2v9jduAQAAAAD9', + }, + }); + const xverseService = new XverseService(); + const xverseTx = { + coin: 'test', + inputs: [ + { + addres: '2Mxv1YkAXpTMcq2at1it9QRfq8bDX82N99J', + idx: 2, + }, + ], + outputs: [ + { + amount: '0', + op_return_data: '52534b5401aFf12FA1c482BEab1D70C68fe0Fc5825447A9818', + }, + { + address: '2N3JQb9erL1SnAr3NTMrZiPQQ8dcjJp4idV', + amount: '500000', + }, + { + address: '2Mxv1YkAXpTMcq2at1it9QRfq8bDX82N99J', + amount: '982474', + }, + ], + base64UnsignedPsbt: 'cHNidP8BAJcCAAAAAboaf2woNcottY/Ax+9lbYi349++WmdwYZPyOp8nXg5tAgAAAAD/////AwAAAAAAAAAAG2oZUlNLVAGv8S+hxIK+qx1wxo/g/FglRHqYGCChBwAAAAAAF6kUbkta6F2G5NsObl2wn4wnYyjNvz+Hyv0OAAAAAAAXqRQ+Lnd+D2z9GnchcMB/v3/pWn8CfYcAAAAAAAEBIL60FgAAAAAAF6kUPi53fg9s/Rp3IXDAf79/6Vp/An2HAQQWABSmO24c7Zf0uweZC7P9rNOrMR7sqwAAAAA=', + }; + expect(xverseService.sign(xverseTx)) + .rejects + .toThrow('Invalid psbt provided'); + }); + + it('should return an error when the psbt has some unsigned input', () => { + sinon.stub(Wallet, 'request').resolves({ + status: 'success', + result: { + psbt: 'cHNidP8BAJcCAAAAAboaf2woNcottY/Ax+9lbYi349++WmdwYZPyOp8nXg5tAgAAAAD/////AwAAAAAAAAAAG2oZUlNLVAGv8S+hxIK+qx1wxo/g/FglRHqYGCChBwAAAAAAF6kUbkta6F2G5NsObl2wn4wnYyjNvz+Hyv0OAAAAAAAXqRQ+Lnd+D2z9GnchcMB/v3/pWn8CfYcAAAAAAAEBIL60FgAAAAAAF6kUPi53fg9s/Rp3IXDAf79/6Vp/An2HAQQWABSmO24c7Zf0uweZC7P9rNOrMR7sqwAAAAA=', + }, + }); + const xverseService = new XverseService(); + const xverseTx = { + coin: 'test', + inputs: [ + { + addres: '2Mxv1YkAXpTMcq2at1it9QRfq8bDX82N99J', + idx: 2, + }, + ], + outputs: [ + { + amount: '0', + op_return_data: '52534b5401aFf12FA1c482BEab1D70C68fe0Fc5825447A9818', + }, + { + address: '2N3JQb9erL1SnAr3NTMrZiPQQ8dcjJp4idV', + amount: '500000', + }, + { + address: '2Mxv1YkAXpTMcq2at1it9QRfq8bDX82N99J', + amount: '982474', + }, + ], + base64UnsignedPsbt: 'cHNidP8BAJcCAAAAAboaf2woNcottY/Ax+9lbYi349++WmdwYZPyOp8nXg5tAgAAAAD/////AwAAAAAAAAAAG2oZUlNLVAGv8S+hxIK+qx1wxo/g/FglRHqYGCChBwAAAAAAF6kUbkta6F2G5NsObl2wn4wnYyjNvz+Hyv0OAAAAAAAXqRQ+Lnd+D2z9GnchcMB/v3/pWn8CfYcAAAAAAAEBIL60FgAAAAAAF6kUPi53fg9s/Rp3IXDAf79/6Vp/An2HAQQWABSmO24c7Zf0uweZC7P9rNOrMR7sqwAAAAA=', + }; + expect(xverseService.sign(xverseTx)) + .rejects + .toThrow('Invalid psbt provided'); + }); +}); From 323964669938a6650997fe90368953d6b8643a3d Mon Sep 17 00:00:00 2001 From: ronaldsg Date: Mon, 23 Sep 2024 19:25:31 -0500 Subject: [PATCH 005/104] Downgrade sats-connect version --- package-lock.json | 90 ++++++++++++++----- package.json | 2 +- ...XverseService.ts => XverseService.spec.ts} | 0 3 files changed, 69 insertions(+), 23 deletions(-) rename tests/unit/common/services/{XverseService.ts => XverseService.spec.ts} (100%) diff --git a/package-lock.json b/package-lock.json index b534f4c51..b8a4e23f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "moment": "^2.29.4", "os-browserify": "^0.3.0", "process": "^0.11.10", - "sats-connect": "^2.8.0", + "sats-connect": "2.3.x", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "text-encoding": "^0.7.0", @@ -7104,16 +7104,25 @@ "license": "ISC" }, "node_modules/@sats-connect/core": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.2.2.tgz", - "integrity": "sha512-nl3zPnV1UBllYAniDfhM/oSFGQ2qy4cCg1YwxJZ+RQMwlTMrVh2f3lJ//dIIo9RgQPrtHpwrAaaWW0VpfqDQbg==", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.0.8.tgz", + "integrity": "sha512-vb7drnd8lFfO4ahCzaVAFkX1eHF1J7jheJl2V/JuuJd5f1sy6nHeNzKMp1zmiuql8uNwe0Sx1WrK1I+4tUmDHg==", "dependencies": { - "axios": "1.7.4", + "axios": "1.6.8", "bitcoin-address-validation": "2.2.3", "buffer": "6.0.3", "jsontokens": "4.0.1", - "lodash.omit": "4.5.0", - "valibot": "0.33.2" + "lodash.omit": "4.5.0" + } + }, + "node_modules/@sats-connect/core/node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, "node_modules/@sats-connect/core/node_modules/bech32": { @@ -26559,28 +26568,70 @@ } }, "node_modules/sats-connect": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-2.8.0.tgz", - "integrity": "sha512-eYdpPoAXn6ud1hMZnQGowO1F0f9fS3jmE5Hq1F3VxXUbAvT2YmA72PBtG6QN/cdMuFZ5x1ce6I/fl270WSXqjw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sats-connect/-/sats-connect-2.3.1.tgz", + "integrity": "sha512-3KzqRO5KVBlge7Q4a/L828SfCkFD+M4MVdtgJZS+L+oHiDYoXlLkvnu3almh9Ynhcm0HnsGmVH1pKVL0lonjyQ==", "dependencies": { - "@sats-connect/core": "0.2.2", - "@sats-connect/make-default-provider-config": "0.0.5", + "@sats-connect/core": "0.0.8", + "@sats-connect/make-default-provider-config": "0.0.4", "@sats-connect/ui": "0.0.6" } }, "node_modules/sats-connect/node_modules/@sats-connect/make-default-provider-config": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@sats-connect/make-default-provider-config/-/make-default-provider-config-0.0.5.tgz", - "integrity": "sha512-b/v4IeDEde5DqFOdMbMmf3B0t/lxlKnY04f3YIUWe1khOg3S6VdcK9Mqva+WUOsJHBTIA5b4hK7CqfMjx1Ic+w==", + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@sats-connect/make-default-provider-config/-/make-default-provider-config-0.0.4.tgz", + "integrity": "sha512-PsLzg1hV3FxMXUp9XrOUmDJgbuyR4VDHq/7mh1O1CtC3dDZQnJFa+Ue43duPMmUaRGinuVKtS2hnMhPLyURdGA==", "dependencies": { - "@sats-connect/ui": "0.0.6", + "@sats-connect/core": "0.0.7", + "@sats-connect/ui": "0.0.5-c661c02", "bowser": "2.11.0" }, "peerDependencies": { - "@sats-connect/core": "*", "typescript": "5.4.4" } }, + "node_modules/sats-connect/node_modules/@sats-connect/make-default-provider-config/node_modules/@sats-connect/core": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@sats-connect/core/-/core-0.0.7.tgz", + "integrity": "sha512-4m5amq+orHDbqLqCRWojvDQigKAys33Ntwc7U5xNtFeib4j+DpYz6lVAL/s3cay1kq03WUZ+Gil3l5rv+5bQWQ==", + "dependencies": { + "axios": "1.6.8", + "bitcoin-address-validation": "2.2.3", + "buffer": "6.0.3", + "jsontokens": "4.0.1", + "lodash.omit": "4.5.0" + } + }, + "node_modules/sats-connect/node_modules/@sats-connect/make-default-provider-config/node_modules/@sats-connect/ui": { + "version": "0.0.5-c661c02", + "resolved": "https://registry.npmjs.org/@sats-connect/ui/-/ui-0.0.5-c661c02.tgz", + "integrity": "sha512-6MUXFDGTapBhZAxb6deAdqKuB64GOe6k927gGww5JYwVnOUCaHGDcfaZ/lwexzYL45u8RJof12I4np7MgS+Bwg==" + }, + "node_modules/sats-connect/node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/sats-connect/node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" + }, + "node_modules/sats-connect/node_modules/bitcoin-address-validation": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz", + "integrity": "sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg==", + "dependencies": { + "base58-js": "^1.0.0", + "bech32": "^2.0.0", + "sha256-uint8array": "^0.10.3" + } + }, "node_modules/sats-connect/node_modules/typescript": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.4.tgz", @@ -29334,11 +29385,6 @@ "node": ">= 8" } }, - "node_modules/valibot": { - "version": "0.33.2", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-0.33.2.tgz", - "integrity": "sha512-ZpFWuI+bs5+PP66q4zVFn4e4t/s5jmMw5iPBZmGUoi8iQqXyU9YY/BLCAyk62Z/bNS8qdUNBEyx52952qdqW3w==" - }, "node_modules/valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", diff --git a/package.json b/package.json index 31f7efe1b..be6b5fc4a 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "moment": "^2.29.4", "os-browserify": "^0.3.0", "process": "^0.11.10", - "sats-connect": "^2.8.0", + "sats-connect": "2.3.x", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "text-encoding": "^0.7.0", diff --git a/tests/unit/common/services/XverseService.ts b/tests/unit/common/services/XverseService.spec.ts similarity index 100% rename from tests/unit/common/services/XverseService.ts rename to tests/unit/common/services/XverseService.spec.ts From 4473834eee55f538194d90be9a980ed2a192bfa8 Mon Sep 17 00:00:00 2001 From: ronaldsg Date: Thu, 26 Sep 2024 02:22:19 -0500 Subject: [PATCH 006/104] Increasin unit test for Xverse wallet services and Tx builder --- package.json | 1 + src/common/services/XverseService.ts | 2 +- .../components/XverseTxBuilder.spec.ts | 182 ++++++++++++++++++ 3 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 tests/unit/pegin/services/components/XverseTxBuilder.spec.ts diff --git a/package.json b/package.json index be6b5fc4a..dfe1d0f3c 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "build": "vue-cli-service build", "test": "vue-cli-service test:unit --env=./tests/jsdom-env.js --coverageReporters=lcov --collect-coverage", "test:unit": "vue-cli-service test:unit --env=./tests/jsdom-env.js --coverageReporters=lcov --collect-coverage", + "test:unit-logs": "vue-cli-service test:unit --env=./tests/jsdom-env.js --coverageReporters=lcov --silent=false --collect-coverage", "lint": "vue-cli-service lint", "scanner": "npx sonar-scanner" }, diff --git a/src/common/services/XverseService.ts b/src/common/services/XverseService.ts index 9725c7edd..d79c667d1 100644 --- a/src/common/services/XverseService.ts +++ b/src/common/services/XverseService.ts @@ -28,7 +28,7 @@ export default class XverseService extends WalletService { const walletAddresses: WalletAddress[] = []; const payload = { purposes: ['payment'] as AddressPurpose[], - message: 'Welcome to the 2wp-app, please select your Bitcoin account to start.', + message: 'Welcome to the Powpeg app, please select your Bitcoin account to start.', network: { type: this.satsBtcNetwork, }, diff --git a/tests/unit/pegin/services/components/XverseTxBuilder.spec.ts b/tests/unit/pegin/services/components/XverseTxBuilder.spec.ts new file mode 100644 index 000000000..32552f841 --- /dev/null +++ b/tests/unit/pegin/services/components/XverseTxBuilder.spec.ts @@ -0,0 +1,182 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { ApiService } from '@/common/services'; +import store from '@/common/store'; +import * as constants from '@/common/store/constants'; +import XverseTxBuilder from '@/pegin/middleware/TxBuilder/XverseTxBuilder'; +import { NormalizedTx, NormalizedInput } from '@/common/types'; +import axios, { AxiosHeaders, AxiosResponse } from 'axios'; +import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; + +const txHex1 = '01000000022d2d5d12644b7f46bd2c094eb663f074f1042fa0a522b59d338cedde83aa5661000000006b483045022100b247c5947c69ab0e6249c587e2792cc5b723a356f796d092b1c2493ed5a74d3a02203e72fc592318ed72b2537859e6d02d91c61da1f27fcb066a566ce20d87e90178012103cb3b06dff127665fc0173c4910b8c254924627cd27decaaf9771f0566b859fdefdffffff57497ac2292f1dec0baba43abbbb02059164309c5f384b64ac4a931c0ae96177010000006a473044022026fa68c558f028258a4f489ecbbe0c5457825f5f7bedaabbcac82b5a64793dc402202aa1996f62728adfe78e1b7b31481397b8bdb20b004b161c3f121a972660746b01210264f3f31d1a81ab1062fce2187dad0c0efb1cee194f5c1ca520bf0a1295df6ee5fdffffff01f44f05000000000017a914a8e241e997f18c2dc0611cc6cb2d889e17f34f378700000000'; + +function getTxHex(expectedHex: string): Promise { + return new Promise((resolve) => { + resolve( + { + data: { hex: expectedHex }, + status: 200, + statusText: 'OK', + headers: {}, + config: { headers: new AxiosHeaders() }, + }, + ); + }); +} + +function setEnvironment() { + const defaultEnvironmentVariables = { + vueAppCoin: constants.BTC_NETWORK_TESTNET, + vueAppRskNodeHost: '', + vueAppApiBaseUrl: 'https://api.2wp.testnet.rootstock.io', + }; + EnvironmentAccessorService.initializeEnvironmentVariables(defaultEnvironmentVariables); +} + +describe('XverseTxBuilder', () => { + let xverseTxBuilder: XverseTxBuilder; + + beforeEach(() => { + setEnvironment(); + xverseTxBuilder = new XverseTxBuilder(); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('buildTx', () => { + it('should build a transaction successfully', async () => { + const normalizedTx: NormalizedTx = { + inputs: [ + { + address: '2N8eCZA1Su5ysSDKqDGSMNH8KgFi1L5jEYh', + prev_hash: 'c31556bf6a6b6c0a57387a88ad69a8cc7c159362d25d4870bffa3c005293375e', + prev_index: 0, + amount: '348148', + }, + ], + outputs: [ + { + address: '2N15XxmYFrp9e1cjCruQVNgYF3cuYQxvtqX', + amount: '1000', + }, + ], + coin: 'test', + }; + store.commit( + `pegInTx/${constants.PEGIN_TX_SET_ADDRESS_LIST}`, + [ + { + derivationPath: "m/49'/1'/0'/1/2", + address: '2N8eCZA1Su5ysSDKqDGSMNH8KgFi1L5jEYh', + publicKey: '02dc57829f1a5001646eb64edcecd797d5cbd0fcb24fb5a37d3084309a9201537c', + unused: false, + }, + { + derivationPath: "m/49'/1'/0'/0/3", + address: '2MzYn6qw7fbV7BtsoT6yBVV8nrbp37f6Uju', + publicKey: '0321afc4e7457e83aedecb1995ea2331d2aeff18a38f14ea710f35924d16d35a30', + unused: true, + }, + ], + ); + sinon.stub(axios, 'get').resolves(getTxHex(txHex1)); + + const result = await xverseTxBuilder.buildTx(normalizedTx); + + expect(result).to.have.property('coin'); + expect(result).to.have.property('inputs').that.is.an('array').with.lengthOf(1); + expect(result).to.have.property('outputs').that.is.an('array').with.lengthOf(1); + expect(result).to.have.property('base64UnsignedPsbt').that.is.a('string'); + }); + + it('should handle errors when building a transaction', async () => { + const normalizedTx: NormalizedTx = { + inputs: [ + { + address: '2N8eCZA1Su5ysSDKqDGSMNH8KgFi1L5jEYh', + prev_hash: 'c31556bf6a6b6c0a57387a88ad69a8cc7c159362d25d4870bffa3c005293375e', + prev_index: 0, + amount: '348148', + }, + ], + outputs: [ + { + address: '2N15XxmYFrp9e1cjCruQVNgYF3cuYQxvtqX', + amount: '1000', + }, + ], + coin: '', + }; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sinon.stub(xverseTxBuilder, 'getExtendedInputs').rejects(new Error('Test error')); + + try { + await xverseTxBuilder.buildTx(normalizedTx); + } catch (error) { + expect(error).to.be.an('error').with.property('message', 'Test error'); + } + }); + }); + + describe('getExtendedInputs', () => { + it('should get extended inputs successfully', async () => { + const normalizedInputs: NormalizedInput[] = [ + { + address: '2N8eCZA1Su5ysSDKqDGSMNH8KgFi1L5jEYh', + prev_hash: 'c31556bf6a6b6c0a57387a88ad69a8cc7c159362d25d4870bffa3c005293375e', + prev_index: 0, + amount: '348148', + }, + ]; + store.commit( + `pegInTx/${constants.PEGIN_TX_SET_ADDRESS_LIST}`, + [ + { + derivationPath: "m/49'/1'/0'/1/2", + address: '2N8eCZA1Su5ysSDKqDGSMNH8KgFi1L5jEYh', + publicKey: '02dc57829f1a5001646eb64edcecd797d5cbd0fcb24fb5a37d3084309a9201537c', + unused: false, + }, + { + derivationPath: "m/49'/1'/0'/0/3", + address: '2MzYn6qw7fbV7BtsoT6yBVV8nrbp37f6Uju', + publicKey: '0321afc4e7457e83aedecb1995ea2331d2aeff18a38f14ea710f35924d16d35a30', + unused: true, + }, + ], + ); + sinon.stub(axios, 'get').resolves(getTxHex(txHex1)); + // eslint-disable-next-line dot-notation + const result = await xverseTxBuilder['getExtendedInputs'](normalizedInputs); + + expect(result).to.be.an('array').with.lengthOf(1); + expect(result[0]).to.have.property('hash', 'c31556bf6a6b6c0a57387a88ad69a8cc7c159362d25d4870bffa3c005293375e'); + expect(result[0]).to.have.property('index', 0); + expect(result[0]).to.have.property('witnessUtxo').that.is.an('object'); + expect(result[0]).to.have.property('redeemScript').that.is.an.instanceOf(Buffer); + }); + + it('should handle errors when getting extended inputs', async () => { + const normalizedInputs: NormalizedInput[] = [ + { + address: 'testAddress1', + prev_hash: 'testHash1', + prev_index: 0, + amount: '', + }, + ]; + + sinon.stub(ApiService, 'getTxHex').rejects(new Error('Tx not found')); + + try { + // eslint-disable-next-line dot-notation + await xverseTxBuilder['getExtendedInputs'](normalizedInputs); + } catch (error) { + expect(error).to.be.an('error').with.property('message', 'Tx not found'); + } + }); + }); +}); From 8569514bbec2683beac48c283643d55b25525a4e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:24:52 +0000 Subject: [PATCH 007/104] build(deps): bump webpack from 5.88.2 to 5.94.0 Bumps [webpack](https://github.com/webpack/webpack) from 5.88.2 to 5.94.0. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.88.2...v5.94.0) --- updated-dependencies: - dependency-name: webpack dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 198 +++++++++++++++++++++++++--------------------- 1 file changed, 107 insertions(+), 91 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8a4e23f4..424fb2642 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5908,8 +5908,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "license": "MIT", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -8150,26 +8151,18 @@ }, "node_modules/@types/eslint": { "version": "8.44.2", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { - "version": "1.0.1", - "devOptional": true, - "license": "MIT" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "devOptional": true }, "node_modules/@types/express": { "version": "4.17.17", @@ -10041,9 +10034,10 @@ } }, "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "devOptional": true, - "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -10051,23 +10045,27 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.6", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "devOptional": true }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "devOptional": true }, "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "devOptional": true, - "license": "MIT" + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "devOptional": true }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "devOptional": true, - "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -10076,62 +10074,69 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.6", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "devOptional": true }, "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "devOptional": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" + "@webassemblyjs/wasm-gen": "1.12.1" } }, "node_modules/@webassemblyjs/ieee754": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "devOptional": true, - "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "devOptional": true, - "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { "version": "1.11.6", - "devOptional": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "devOptional": true }, "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "devOptional": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "devOptional": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", "@webassemblyjs/leb128": "1.11.6", @@ -10139,22 +10144,24 @@ } }, "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "devOptional": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "devOptional": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@webassemblyjs/helper-api-error": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", "@webassemblyjs/ieee754": "1.11.6", @@ -10163,11 +10170,12 @@ } }, "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "devOptional": true, - "license": "MIT", "dependencies": { - "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/ast": "1.12.1", "@xtuc/long": "4.2.2" } }, @@ -10181,13 +10189,15 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "devOptional": true, - "license": "BSD-3-Clause" + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "devOptional": true }, "node_modules/@xtuc/long": { "version": "4.2.2", - "devOptional": true, - "license": "Apache-2.0" + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "devOptional": true }, "node_modules/abab": { "version": "2.0.6", @@ -10270,10 +10280,11 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "devOptional": true, - "license": "MIT", "peerDependencies": { "acorn": "^8" } @@ -28169,9 +28180,10 @@ } }, "node_modules/terser": { - "version": "5.19.2", + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", "devOptional": true, - "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -28186,15 +28198,16 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.9", + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", "devOptional": true, - "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", + "@jridgewell/trace-mapping": "^0.3.20", "jest-worker": "^27.4.5", "schema-utils": "^3.1.1", "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" + "terser": "^5.26.0" }, "engines": { "node": ">= 10.13.0" @@ -29824,9 +29837,10 @@ } }, "node_modules/watchpack": { - "version": "2.4.0", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", "devOptional": true, - "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -31406,33 +31420,33 @@ } }, "node_modules/webpack": { - "version": "5.88.2", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "devOptional": true, - "license": "MIT", "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", - "browserslist": "^4.14.5", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.15.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", + "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.2.0", "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", "webpack-sources": "^3.2.3" }, "bin": { @@ -31858,9 +31872,10 @@ "license": "MIT" }, "node_modules/webpack/node_modules/enhanced-resolve": { - "version": "5.15.0", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "devOptional": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -31888,8 +31903,9 @@ }, "node_modules/webpack/node_modules/tapable": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "devOptional": true, - "license": "MIT", "engines": { "node": ">=6" } From 070592d811f3edb8727acc9a26dbf164c72fd0f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:21:50 +0000 Subject: [PATCH 008/104] build(deps): bump body-parser and express Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `body-parser` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: body-parser dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 244 +++++++++++++++++++++++++++++++++------------- 1 file changed, 176 insertions(+), 68 deletions(-) diff --git a/package-lock.json b/package-lock.json index 424fb2642..065713c05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11487,9 +11487,9 @@ "license": "MIT" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dev": true, "dependencies": { "bytes": "3.1.2", @@ -11500,7 +11500,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -11525,21 +11525,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bonjour-service": { "version": "1.1.1", "dev": true, @@ -11845,8 +11830,9 @@ }, "node_modules/bytes": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -12728,8 +12714,9 @@ }, "node_modules/content-type": { "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -14261,6 +14248,7 @@ "node_modules/encodeurl": { "version": "1.0.2", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -16707,37 +16695,37 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -16757,6 +16745,15 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -16764,25 +16761,50 @@ "dev": true }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "dev": true, - "license": "MIT" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "dev": true }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "node_modules/express/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "dependencies": { - "side-channel": "^1.0.4" + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" } }, + "node_modules/express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -17130,12 +17152,13 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, - "license": "MIT", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -17148,16 +17171,27 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.0.0" } }, - "node_modules/finalhandler/node_modules/ms": { + "node_modules/finalhandler/node_modules/encodeurl": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, - "license": "MIT" + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true }, "node_modules/find-cache-dir": { "version": "3.3.2", @@ -22993,8 +23027,9 @@ }, "node_modules/media-typer": { "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -23045,9 +23080,13 @@ "license": "MIT" }, "node_modules/merge-descriptors": { - "version": "1.0.1", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, - "license": "MIT" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-source-map": { "version": "1.1.0", @@ -25649,10 +25688,11 @@ } }, "node_modules/qs": { - "version": "6.11.2", - "license": "BSD-3-Clause", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -26777,6 +26817,7 @@ "node_modules/send": { "version": "0.18.0", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -26799,17 +26840,20 @@ "node_modules/send/node_modules/debug": { "version": "2.6.9", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/send/node_modules/ms": { "version": "2.1.3", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/serialize-error": { "version": "7.0.1", @@ -26917,18 +26961,76 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "license": "MIT", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "license": "ISC" @@ -27041,12 +27143,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "license": "MIT", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -28844,8 +28951,9 @@ }, "node_modules/type-is": { "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, - "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" From 1ffcabe9e4df40e421b9b58f6a2507685ba3920c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:21:10 +0000 Subject: [PATCH 009/104] build(deps): bump path-to-regexp and express Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together. Updates `path-to-regexp` from 1.8.0 to 1.9.0 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v1.8.0...v1.9.0) Updates `express` from 4.19.2 to 4.21.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 065713c05..3a8e1e8b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24502,9 +24502,10 @@ "license": "MIT" }, "node_modules/path-to-regexp": { - "version": "1.8.0", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dev": true, - "license": "MIT", "dependencies": { "isarray": "0.0.1" } From 4844762a6a8485828af2894d1169af76da2e77e0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:24:45 +0000 Subject: [PATCH 010/104] build(deps): bump micromatch from 4.0.5 to 4.0.8 Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3a8e1e8b7..06c825eec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23188,10 +23188,11 @@ "license": "MIT" }, "node_modules/micromatch": { - "version": "4.0.5", - "license": "MIT", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { From c87961254778427234c3cbdb43abb2eac0f2c097 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Thu, 26 Sep 2024 17:13:01 -0300 Subject: [PATCH 011/104] Change displayed amount for flyover pegouts --- src/common/views/SuccessTx.vue | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/common/views/SuccessTx.vue b/src/common/views/SuccessTx.vue index d4005dfcd..21878fc1c 100644 --- a/src/common/views/SuccessTx.vue +++ b/src/common/views/SuccessTx.vue @@ -51,7 +51,9 @@ import { defineComponent, computed, PropType } from 'vue'; import { useAction, useGetter, useState } from '@/common/store/helper'; import * as constants from '@/common/store/constants'; import { useRouter } from 'vue-router'; -import { PegInTxState, SatoshiBig, TxStatusType } from '@/common/types'; +import { + PegInTxState, PegOutTxState, SatoshiBig, TxStatusType, +} from '@/common/types'; import EnvironmentContextProviderService from '@/common/providers/EnvironmentContextProvider'; export default defineComponent({ @@ -70,12 +72,23 @@ export default defineComponent({ const clearStatus = useAction('status', constants.STATUS_CLEAR); const router = useRouter(); const pegInTxState = useState('pegInTx'); + const pegOutTxState = useState('pegOutTx'); const environmentContext = EnvironmentContextProviderService.getEnvironmentContext(); const estimatedBtcToReceive = useGetter('pegOutTx', constants.PEGOUT_TX_GET_ESTIMATED_BTC_TO_RECEIVE); - const amount = computed(() => (props.type === (TxStatusType.PEGIN).toLowerCase() - ? pegInTxState.value.amountToTransfer.toBTCTrimmedString() - : estimatedBtcToReceive.value.toBTCTrimmedString())); + const amount = computed(() => { + switch (props.type.toUpperCase()) { + case TxStatusType.PEGIN || TxStatusType.FLYOVER_PEGIN: + return pegInTxState.value.amountToTransfer.toBTCTrimmedString(); + case TxStatusType.PEGOUT: + return estimatedBtcToReceive.value.toBTCTrimmedString(); + case TxStatusType.FLYOVER_PEGOUT: + return pegOutTxState.value.amountToTransfer.toRBTCTrimmedString(); + default: + return ''; + } + }); + const symbol = computed(() => (props.type === (TxStatusType.PEGIN).toLowerCase() ? environmentContext.getRbtcTicker() : environmentContext.getBtcTicker())); From 81e8784cc5d161432ea6f4bd7a8a74292e6c7824 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Thu, 26 Sep 2024 17:13:39 -0300 Subject: [PATCH 012/104] Remove old confirmation page --- src/pegout/components/Confirmation.vue | 135 ------------------------ src/pegout/components/FlyoverPegout.vue | 4 +- src/pegout/views/PegOut.vue | 2 - 3 files changed, 1 insertion(+), 140 deletions(-) delete mode 100644 src/pegout/components/Confirmation.vue diff --git a/src/pegout/components/Confirmation.vue b/src/pegout/components/Confirmation.vue deleted file mode 100644 index d83a33e77..000000000 --- a/src/pegout/components/Confirmation.vue +++ /dev/null @@ -1,135 +0,0 @@ - - - diff --git a/src/pegout/components/FlyoverPegout.vue b/src/pegout/components/FlyoverPegout.vue index 6bcf00ebb..55ae624d4 100644 --- a/src/pegout/components/FlyoverPegout.vue +++ b/src/pegout/components/FlyoverPegout.vue @@ -136,8 +136,7 @@ export default defineComponent({ required: true, }, }, - setup(props, context) { - const nextPage = 'Confirmation'; + setup(props) { const showTxErrorDialog = ref(false); const txError = ref(new ServiceError('', '', '', '')); const environmentContext = EnvironmentContextProviderService.getEnvironmentContext(); @@ -264,7 +263,6 @@ export default defineComponent({ : pegOutTxState.value.txHash, }, }); - context.emit('changePage', nextPage); } function getLPName(): string { diff --git a/src/pegout/views/PegOut.vue b/src/pegout/views/PegOut.vue index bcd5ad695..619a87980 100644 --- a/src/pegout/views/PegOut.vue +++ b/src/pegout/views/PegOut.vue @@ -15,13 +15,11 @@ import FlyoverPegout from '@/pegout/components/FlyoverPegout.vue'; import { Machine } from '@/common/utils'; import { useAction, useGetter } from '@/common/store/helper'; import { Feature, FeatureNames } from '@/common/types'; -import Confirmation from '../components/Confirmation.vue'; export default defineComponent({ name: 'PegOut', components: { PegOutForm, - Confirmation, FlyoverPegout, }, setup() { From da837000340dabccb6728c260638df9552b84302 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Mon, 30 Sep 2024 14:31:12 -0300 Subject: [PATCH 013/104] Show btc tx id of successful pegout --- src/common/components/status/TxPegout.vue | 2 ++ src/common/types/Common.ts | 1 + src/common/types/store.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/src/common/components/status/TxPegout.vue b/src/common/components/status/TxPegout.vue index 70641a628..0266fc2a9 100644 --- a/src/common/components/status/TxPegout.vue +++ b/src/common/components/status/TxPegout.vue @@ -61,6 +61,7 @@ export default defineComponent({ const status = txDetails.value as PegoutStatusDataModel; const valueRequested = new SatoshiBig(status.valueRequestedInSatoshis, 'satoshi').toBTCTrimmedString(); const amountSent = new WeiBig(valueRequested, 'rbtc').plus(calculatedGasFee.value).toRBTCTrimmedString(); + const btcTxId = status.status === PegoutStatus.RELEASE_BTC ? status.btcTxId : ''; return { amountFromString: amountSent, amountReceivedString: amountToBeReceived.value, @@ -71,6 +72,7 @@ export default defineComponent({ txId: status.rskTxHash ? status.rskTxHash : props.txId, estimatedFee: Number(pegOutEstimatedFee.value.toBTCTrimmedString()), status: status.status, + btcTxId, }; }); diff --git a/src/common/types/Common.ts b/src/common/types/Common.ts index 182b7871d..a7a7613f6 100644 --- a/src/common/types/Common.ts +++ b/src/common/types/Common.ts @@ -126,6 +126,7 @@ export interface NormalizedSummary { federationAddress?: string; total?: string; status?: PegStatus | PegoutStatus; + btcTxId?: string; } export type AddressType = 'BITCOIN_LEGACY_ADDRESS' | 'BITCOIN_SEGWIT_ADDRESS' | 'BITCOIN_NATIVE_SEGWIT_ADDRESS' | diff --git a/src/common/types/store.ts b/src/common/types/store.ts index b67a43e60..fea932829 100644 --- a/src/common/types/store.ts +++ b/src/common/types/store.ts @@ -66,6 +66,7 @@ export interface PegoutStatusDataModel { btcRawTransaction: string; fees: number; estimatedFee: SatoshiBig; + btcTxId: string; } export interface FlyoverStatusModel { From 59624cc8d31b499475338cefe89fbc9ac00e8845 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Mon, 30 Sep 2024 16:51:46 -0300 Subject: [PATCH 014/104] Change success text and btc fee estimation on status page --- src/common/utils/utils.ts | 7 ++++++- src/common/views/SuccessTx.vue | 21 ++++++--------------- src/status/store/actions.ts | 2 +- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/common/utils/utils.ts b/src/common/utils/utils.ts index cccd51d6c..5bfb79333 100644 --- a/src/common/utils/utils.ts +++ b/src/common/utils/utils.ts @@ -73,7 +73,7 @@ export function getRskAddressExplorerUrl(address: string) { return `${getRskBaseExplorerUrl()}/address/${address}`; } -export function getEstimatedFee(): Promise { +export function getEstimatedFee(pegoutAlreadyRequested = false): Promise { return new Promise((resolve, reject) => { const bridgeService = new BridgeService(); Promise.all([ @@ -81,6 +81,11 @@ export function getEstimatedFee(): Promise { bridgeService.getQueuedPegoutsCount(), ]) .then(([nextPegoutCost, pegoutQueueCount]) => { + if (pegoutAlreadyRequested && pegoutQueueCount !== 0n) { + const currentEstimatedFee = nextPegoutCost / pegoutQueueCount; + resolve(new SatoshiBig(currentEstimatedFee, 'satoshi')); + return; + } const estimatedFee = nextPegoutCost / (pegoutQueueCount + 1n); resolve(new SatoshiBig(estimatedFee, 'satoshi')); }) diff --git a/src/common/views/SuccessTx.vue b/src/common/views/SuccessTx.vue index 21878fc1c..c561394a5 100644 --- a/src/common/views/SuccessTx.vue +++ b/src/common/views/SuccessTx.vue @@ -10,11 +10,7 @@
- - You will receive {{ amount }} {{ symbol }} on your address -
-
- + {{ willReceiveText }}
@@ -76,23 +72,19 @@ export default defineComponent({ const environmentContext = EnvironmentContextProviderService.getEnvironmentContext(); const estimatedBtcToReceive = useGetter('pegOutTx', constants.PEGOUT_TX_GET_ESTIMATED_BTC_TO_RECEIVE); - const amount = computed(() => { + const willReceiveText = computed(() => { switch (props.type.toUpperCase()) { case TxStatusType.PEGIN || TxStatusType.FLYOVER_PEGIN: - return pegInTxState.value.amountToTransfer.toBTCTrimmedString(); + return `You will receive ${pegInTxState.value.amountToTransfer.toBTCTrimmedString()} ${environmentContext.getRbtcTicker()}`; case TxStatusType.PEGOUT: - return estimatedBtcToReceive.value.toBTCTrimmedString(); + return `You will receive approximately ${estimatedBtcToReceive.value.toBTCTrimmedString()} ${environmentContext.getBtcTicker()}`; case TxStatusType.FLYOVER_PEGOUT: - return pegOutTxState.value.amountToTransfer.toRBTCTrimmedString(); + return `You will receive ${pegOutTxState.value.amountToTransfer.toRBTCTrimmedString()} ${environmentContext.getBtcTicker()}`; default: return ''; } }); - const symbol = computed(() => (props.type === (TxStatusType.PEGIN).toLowerCase() - ? environmentContext.getRbtcTicker() - : environmentContext.getBtcTicker())); - function goHome() { clearStatus(); router.push({ name: 'Home' }); @@ -108,8 +100,7 @@ export default defineComponent({ return { mdiContentCopy, - amount, - symbol, + willReceiveText, pegInTxState, copyToClipboard, goHome, diff --git a/src/status/store/actions.ts b/src/status/store/actions.ts index e40aff762..e1c29b925 100644 --- a/src/status/store/actions.ts +++ b/src/status/store/actions.ts @@ -32,7 +32,7 @@ export const actions: ActionTree = { }, [constants.STATUS_GET_ESTIMATED_FEE]: async ({ commit }) => { try { - const estimatedFee = await getEstimatedFee(); + const estimatedFee = await getEstimatedFee(true); commit(constants.STATUS_SET_BTC_ESTIMATED_FEE, new SatoshiBig(estimatedFee, 'satoshi')); } catch (e) { commit(constants.STATUS_SET_BTC_ESTIMATED_FEE, new SatoshiBig(0, 'satoshi')); From b92b935017003089b491f07ffe5d908afb8efaf1 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Thu, 19 Sep 2024 16:51:54 -0300 Subject: [PATCH 015/104] Get flyover tx status based on quote hash --- .../components/status/PegoutProgressBar.vue | 166 ------------------ .../components/status/StatusProgressBar.vue | 87 +++++++-- src/common/store/constants.ts | 5 +- src/common/types/store.ts | 10 +- src/common/utils/utils.ts | 4 +- src/status/store/actions.ts | 67 +++++-- src/status/store/mutations.ts | 3 + src/status/views/Status.vue | 12 +- 8 files changed, 157 insertions(+), 197 deletions(-) delete mode 100644 src/common/components/status/PegoutProgressBar.vue diff --git a/src/common/components/status/PegoutProgressBar.vue b/src/common/components/status/PegoutProgressBar.vue deleted file mode 100644 index c5f3fc216..000000000 --- a/src/common/components/status/PegoutProgressBar.vue +++ /dev/null @@ -1,166 +0,0 @@ - - - diff --git a/src/common/components/status/StatusProgressBar.vue b/src/common/components/status/StatusProgressBar.vue index 623c2b798..92f39193c 100644 --- a/src/common/components/status/StatusProgressBar.vue +++ b/src/common/components/status/StatusProgressBar.vue @@ -6,10 +6,9 @@
+ :id="`indicator-${timeLineData[0][1]}${txFailed?'-error':''}`">
- @@ -37,7 +35,6 @@

{{ timeLineData[0][0] }}

-

{{ timeLineData[4][0] }}

@@ -73,6 +69,7 @@ import { TxStatusType, } from '@/common/types'; import { PegStatus, FlyoverStatus } from '@/common/store/constants'; +import EnvironmentContextProviderService from '@/common/providers/EnvironmentContextProvider'; export default defineComponent({ name: 'StatusProgressBar', @@ -83,6 +80,11 @@ export default defineComponent({ }, setup(props) { const status = useState('status'); + const environmentContext = EnvironmentContextProviderService.getEnvironmentContext(); + const btcTicker = environmentContext.getBtcTicker(); + const rbtcTicker = environmentContext.getRbtcTicker(); + const txFailed = computed(() => props.txWithError + || status.value.flyoverStatus === FlyoverStatus.FAILED); const txDetails = useStateAttribute('status', 'txDetails'); const isPegOut = computed((): boolean => status.value.type === TxStatusType.PEGOUT || status.value.type === TxStatusType.FLYOVER_PEGOUT); @@ -99,8 +101,8 @@ export default defineComponent({ } return require('@/assets/status/rbtc.svg'); }); - const initialImgSize = computed(() => (props.txWithError ? 32 : 12)); - const barColor = computed(() => (props.txWithError ? 'red' : 'green')); + const initialImgSize = computed(() => (txFailed.value ? 32 : 12)); + const barColor = computed(() => (txFailed.value ? 'red' : 'green')); const timeLineData = computed(() => { let labelOne = 'Transaction Broadcasted'; let labelTwo = 'Transaction Confirmed'; @@ -116,7 +118,38 @@ export default defineComponent({ } else if (isPegOut.value) { labelThree = 'Sent to Bitcoin'; if (props.isFlyover) { - zero = txDetails.value.status === FlyoverStatus.COMPLETED ? 100 : 50; + labelOne = `Send ${rbtcTicker} to Liquidity Provider`; + labelTwo = `Liquidity Provider received ${rbtcTicker}`; + labelThree = `Liquidity Provider send ${btcTicker}`; + switch (status.value.flyoverStatus) { + case FlyoverStatus.PENDING: + zero = 100; + first = 100; + second = 70; + break; + case FlyoverStatus.SUCCESS: + zero = 100; + first = 100; + second = 100; + third = 100; + break; + case FlyoverStatus.FAILED: + zero = 100; + labelOne = 'Error occurred'; + labelTwo = ''; + labelThree = ''; + break; + default: + zero = 0; + first = 0; + second = 0; + third = 0; + fourth = 0; + labelOne = ''; + labelTwo = ''; + labelThree = ''; + break; + } } else { switch (txDetails.value.status as PegoutStatus) { case PegoutStatus.PENDING: @@ -166,7 +199,38 @@ export default defineComponent({ } } } else if (props.isFlyover) { - zero = txDetails.value.status === FlyoverStatus.COMPLETED ? 100 : 50; + labelOne = `Send ${btcTicker} to Liquidity Provider`; + labelTwo = `Liquidity Provider received ${btcTicker}`; + labelThree = `Liquidity Provider send ${rbtcTicker}`; + switch (status.value.flyoverStatus) { + case FlyoverStatus.PENDING: + zero = 100; + first = 100; + second = 70; + break; + case FlyoverStatus.SUCCESS: + zero = 100; + first = 100; + second = 100; + third = 100; + break; + case FlyoverStatus.FAILED: + zero = 100; + labelOne = 'Error occurred'; + labelTwo = ''; + labelThree = ''; + break; + default: + zero = 0; + first = 0; + second = 0; + third = 0; + fourth = 0; + labelOne = ''; + labelTwo = ''; + labelThree = ''; + break; + } } else { const txDetailsPegIn = txDetails.value as PeginStatus; const btc = txDetailsPegIn.btc as BtcPeginStatus; @@ -240,7 +304,7 @@ export default defineComponent({ }; }); const imgStep1 = computed(() => { - if (props.txWithError) return require('@/assets/status/ellipse_error.svg'); + if (txFailed.value) return require('@/assets/status/ellipse_error.svg'); if (timeLineData.value[0][1] === 100) return require('@/assets/status/ellipse.svg'); return require('@/assets/status/ellipse_empty.svg'); }); @@ -266,6 +330,7 @@ export default defineComponent({ imgStep3, initialImgSize, barColor, + txFailed, }; }, }); diff --git a/src/common/store/constants.ts b/src/common/store/constants.ts index 2b02be99d..c4b4ec06d 100644 --- a/src/common/store/constants.ts +++ b/src/common/store/constants.ts @@ -262,6 +262,7 @@ export const STATUS_CLEAR = 'STATUS_CLEAR'; export const PEGOUT_TX_ADD_BITCOIN_PRICE = 'PEGOUT_TX_ADD_BITCOIN_PRICE'; export const STATUS_GET_ESTIMATED_FEE = 'STATUS_GET_ESTIMATED_FEE'; export const STATUS_GET_ESTIMATED_RELEASE_TIME_IN_MINUTES = 'STATUS_GET_ESTIMATED_RELEASE_TIME_IN_MINUTES'; +export const STATUS_GET_FLYOVER_STATUS = 'STATUS_GET_FLYOVER_STATUS'; // Status mutations export const STATUS_SET_TX_DETAILS = 'STATUS_SET_TX_DETAILS'; @@ -269,6 +270,7 @@ export const STATUS_SET_TX_TYPE = 'STATUS_SET_TX_TYPE'; export const STATUS_SET_CLEAR = 'STATUS_SET_CLEAR'; export const STATUS_SET_BTC_ESTIMATED_FEE = 'STATUS_SET_BTC_ESTIMATED_FEE'; export const STATUS_SET_ESTIMATED_RELEASE_TIME_IN_MINUTES = 'STATUS_SET_ESTIMATED_RELEASE_TIME_IN_MINUTES'; +export const STATUS_SET_FLYOVER_STATUS = 'STATUS_SET_FLYOVER_STATUS'; // Status getters export const STATUS_IS_REJECTED = 'STATUS_IS_REJECTED'; @@ -291,7 +293,8 @@ export enum PegStatus { export enum FlyoverStatus { PENDING = 'PENDING', - COMPLETED = 'COMPLETED', + SUCCESS = 'SUCCESS', + FAILED = 'FAILED', } export const LEDGER_STATUS_CODES = { diff --git a/src/common/types/store.ts b/src/common/types/store.ts index fea932829..345f1b6c6 100644 --- a/src/common/types/store.ts +++ b/src/common/types/store.ts @@ -3,13 +3,19 @@ import { PegStatus } from '@/common/store/constants'; import SatoshiBig from '@/common/types/SatoshiBig'; import { PegInTxState } from '@/common/types/pegInTx'; import { SessionState } from '@/common/types/session'; -import { PegOutTxState } from './pegOutTx'; +import { PegOutTxState } from '@/common/types/pegOutTx'; +import { FlyoverPeginState } from '@/common/types/FlyoverPegin'; +import { FlyoverPegoutState } from '@/common/types/FlyoverPegout'; +import { StatusState } from '@/common/types/Status'; export interface RootState { pegInTx?: PegInTxState, web3Session?: SessionState, pegOutTx?: PegOutTxState, version: string; + status?: StatusState; + flyoverPegin?: FlyoverPeginState; + flyoverPegout?: FlyoverPegoutState; } export interface BtcPeginStatus { @@ -79,6 +85,7 @@ export interface FlyoverStatusModel { status: string; senderAddress: string; recipientAddress: string; + quoteHash: string; } export enum TxStatusType { @@ -96,4 +103,5 @@ export interface TxStatus { type: TxStatusType; pegOutEstimatedFee: SatoshiBig; estimatedReleaseTimeInMinutes: Duration; + flyoverStatus?: string; } diff --git a/src/common/utils/utils.ts b/src/common/utils/utils.ts index 5bfb79333..0de917604 100644 --- a/src/common/utils/utils.ts +++ b/src/common/utils/utils.ts @@ -199,7 +199,7 @@ export function setStatusMessage(txType: string, status: string): TxStatusMessag activeMessageStyle = 'statusProgress'; isRejected = false; break; - case constants.FlyoverStatus.COMPLETED: + case constants.FlyoverStatus.SUCCESS: statusMessage = 'Your transaction was successfully processed!'; activeMessageStyle = 'statusSuccess'; isRejected = false; @@ -214,7 +214,7 @@ export function setStatusMessage(txType: string, status: string): TxStatusMessag activeMessageStyle = 'statusProgress'; isRejected = false; break; - case constants.FlyoverStatus.COMPLETED: + case constants.FlyoverStatus.SUCCESS: statusMessage = 'Your transaction was successfully processed!'; activeMessageStyle = 'statusSuccess'; isRejected = false; diff --git a/src/status/store/actions.ts b/src/status/store/actions.ts index e1c29b925..b124a9f5c 100644 --- a/src/status/store/actions.ts +++ b/src/status/store/actions.ts @@ -2,6 +2,7 @@ import { ActionTree } from 'vuex'; import Web3 from 'web3'; import moment from 'moment'; import { + FlyoverStatusModel, PegoutStatusDataModel, RootState, SatoshiBig, TxStatus, TxStatusType, } from '@/common/types'; @@ -15,21 +16,33 @@ export const actions: ActionTree = { [constants.STATUS_CLEAR]: ({ commit }) => { commit(constants.STATUS_SET_CLEAR); }, - [constants.STATUS_GET_TX_STATUS]: ({ commit, dispatch }, txId: string) => { - Promise.all([ - ApiService.getTxStatus(txId), - dispatch(constants.STATUS_GET_ESTIMATED_FEE), - ]) - .then(([status]) => { - commit(constants.STATUS_SET_TX_DETAILS, status.txDetails); - commit(constants.STATUS_SET_TX_TYPE, status.type); - return dispatch(constants.STATUS_GET_ESTIMATED_RELEASE_TIME_IN_MINUTES); - }) - .catch(() => { - commit(constants.STATUS_SET_TX_DETAILS, undefined); - commit(constants.STATUS_SET_TX_TYPE, TxStatusType.UNEXPECTED_ERROR); - }); - }, + [constants.STATUS_GET_TX_STATUS]: + ({ commit, dispatch }, txId: string) => new Promise((resolve, reject) => { + Promise.all([ + ApiService.getTxStatus(txId), + dispatch(constants.STATUS_GET_ESTIMATED_FEE), + ]) + .then(([status]) => { + commit(constants.STATUS_SET_TX_DETAILS, status.txDetails); + commit(constants.STATUS_SET_TX_TYPE, status.type); + const nextActions = []; + if (status.type === TxStatusType.FLYOVER_PEGIN + || status.type === TxStatusType.FLYOVER_PEGOUT) { + nextActions.push(dispatch( + constants.STATUS_GET_FLYOVER_STATUS, + (status.txDetails as FlyoverStatusModel).quoteHash, + )); + } + nextActions.push(dispatch(constants.STATUS_GET_ESTIMATED_RELEASE_TIME_IN_MINUTES)); + return Promise.all(nextActions); + }) + .then(resolve) + .catch(() => { + commit(constants.STATUS_SET_TX_DETAILS, undefined); + commit(constants.STATUS_SET_TX_TYPE, TxStatusType.UNEXPECTED_ERROR); + reject(); + }); + }), [constants.STATUS_GET_ESTIMATED_FEE]: async ({ commit }) => { try { const estimatedFee = await getEstimatedFee(true); @@ -64,4 +77,28 @@ export const actions: ActionTree = { } resolve(); }), + [constants.STATUS_GET_FLYOVER_STATUS]: async ({ + state, commit, dispatch, rootState, + }, quoteHash) => { + let status; + try { + if (state.type === TxStatusType.FLYOVER_PEGIN) { + const flyoverService = rootState.flyoverPegin?.flyoverService; + await dispatch(`flyoverPegin/${constants.FLYOVER_PEGIN_INIT}`, null, { root: true }); + flyoverService?.useLiquidityProvider(1); + status = await flyoverService?.getPeginStatus(quoteHash); + } + if (state.type === TxStatusType.FLYOVER_PEGOUT) { + const flyoverService = rootState.flyoverPegout?.flyoverService; + await dispatch(`flyoverPegout/${constants.FLYOVER_PEGOUT_INIT}`, null, { root: true }); + await dispatch(`flyoverPegout/${constants.FLYOVER_PEGOUT_GET_PROVIDERS}`, null, { root: true }); + flyoverService?.useLiquidityProvider(1); + status = await rootState.flyoverPegout?.flyoverService.getPegoutStatus(quoteHash); + } + } catch (e) { + status = 'NOT_FOUND'; + } finally { + commit(constants.STATUS_SET_FLYOVER_STATUS, status); + } + }, }; diff --git a/src/status/store/mutations.ts b/src/status/store/mutations.ts index 433bc1a9d..bf58e2859 100644 --- a/src/status/store/mutations.ts +++ b/src/status/store/mutations.ts @@ -26,4 +26,7 @@ export const mutations: MutationTree = { (state: TxStatus, releaseTime: Duration) => { state.estimatedReleaseTimeInMinutes = releaseTime; }, + [constants.STATUS_SET_FLYOVER_STATUS]: (state, status) => { + state.flyoverStatus = status; + }, }; diff --git a/src/status/views/Status.vue b/src/status/views/Status.vue index 531e7875a..1edf6c62a 100644 --- a/src/status/views/Status.vue +++ b/src/status/views/Status.vue @@ -18,7 +18,7 @@ Estimated time: {{ releaseTimeText }} - + + + + Searching... + + From 184ca01ac2e6a901f4e86461fff347d47aa343e8 Mon Sep 17 00:00:00 2001 From: Anni Piragauta Date: Thu, 3 Oct 2024 11:24:29 -0500 Subject: [PATCH 016/104] feature: adding enkrypt wallet for pegin --- package-lock.json | 9 ++ package.json | 1 + .../exchange/enkrypt/connect_enkrypt.png | Bin 0 -> 91271 bytes src/assets/wallet-icons/enkrypt-black.svg | 3 + src/assets/wallet-icons/enkrypt-color.svg | 9 ++ src/assets/wallet-icons/enkrypt-white.svg | 3 + .../components/exchange/ConnectDevice.vue | 4 + .../exchange/SelectBitcoinWallet.vue | 3 + src/common/services/EnkryptService.ts | 91 ++++++++++++++++ src/common/services/LeatherService.ts | 27 ----- src/common/services/LedgerService.ts | 88 +-------------- src/common/services/TrezorService.ts | 87 --------------- src/common/services/WalletService.ts | 4 +- src/common/services/index.ts | 1 + src/common/store/constants.ts | 1 + src/common/types/pegInTx.ts | 2 +- src/common/walletConf.json | 12 +++ .../components/create/PegInAccountSelect.vue | 1 + src/pegin/components/create/SendBitcoin.vue | 5 + .../middleware/TxBuilder/EnkryptTxBuilder.ts | 80 ++++++++++++++ src/pegin/store/PeginTx/actions.ts | 6 +- src/pegin/store/PeginTx/getters.ts | 6 ++ src/pegout/components/FlyoverPegout.vue | 1 - src/shims-tsx.d.ts | 5 + tests/unit/SatoshiBig.spec.ts | 2 +- .../common/services/EnkryptService.spec.ts | 101 ++++++++++++++++++ .../pegin/services/EnkryptTxBuilder.spec.ts | 67 ++++++++++++ 27 files changed, 411 insertions(+), 208 deletions(-) create mode 100644 src/assets/exchange/enkrypt/connect_enkrypt.png create mode 100644 src/assets/wallet-icons/enkrypt-black.svg create mode 100644 src/assets/wallet-icons/enkrypt-color.svg create mode 100644 src/assets/wallet-icons/enkrypt-white.svg create mode 100644 src/common/services/EnkryptService.ts create mode 100644 src/pegin/middleware/TxBuilder/EnkryptTxBuilder.ts create mode 100644 tests/unit/common/services/EnkryptService.spec.ts create mode 100644 tests/unit/pegin/services/EnkryptTxBuilder.spec.ts diff --git a/package-lock.json b/package-lock.json index 06c825eec..b6369b1f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "PowPeg", "version": "2.3.0", "dependencies": { + "@enkryptcom/types": "^0.0.5", "@leather.io/rpc": "^2.0.2", "@ledgerhq/devices": "6.27.1", "@ledgerhq/hw-app-btc": "6.27.1", @@ -2277,6 +2278,14 @@ "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-nodejs/-/cardano-serialization-lib-nodejs-11.5.0.tgz", "integrity": "sha512-IlVABlRgo9XaTR1NunwZpWcxnfEv04ba2l1vkUz4S1W7Jt36F4CtffP+jPeqBZGnAe+fnUwo0XjIJC3ZTNToNQ==" }, + "node_modules/@enkryptcom/types": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@enkryptcom/types/-/types-0.0.5.tgz", + "integrity": "sha512-GzSq2pu7LO1iuAcKFUORd+SyGm3907/ZHupAn/49sRbLXfJZ3HzTY65jeHG/EWPL4lyv2xyuhy7JEpo+x0tIUQ==", + "engines": { + "node": ">=14.15.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "dev": true, diff --git a/package.json b/package.json index dfe1d0f3c..69d92ef9b 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "scanner": "npx sonar-scanner" }, "dependencies": { + "@enkryptcom/types": "^0.0.5", "@leather.io/rpc": "^2.0.2", "@ledgerhq/devices": "6.27.1", "@ledgerhq/hw-app-btc": "6.27.1", diff --git a/src/assets/exchange/enkrypt/connect_enkrypt.png b/src/assets/exchange/enkrypt/connect_enkrypt.png new file mode 100644 index 0000000000000000000000000000000000000000..61a6a9b12e97a7b69d1fa1b832e9f9316381faad GIT binary patch literal 91271 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%0DD6k1!*uN6-0&yMp~+At#WKO1BddQM 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 Or*>caUfCU+$o~iZP&1DJ literal 0 HcmV?d00001 diff --git a/src/assets/wallet-icons/enkrypt-black.svg b/src/assets/wallet-icons/enkrypt-black.svg new file mode 100644 index 000000000..e6f7afaba --- /dev/null +++ b/src/assets/wallet-icons/enkrypt-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/wallet-icons/enkrypt-color.svg b/src/assets/wallet-icons/enkrypt-color.svg new file mode 100644 index 000000000..80d3e36d0 --- /dev/null +++ b/src/assets/wallet-icons/enkrypt-color.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/assets/wallet-icons/enkrypt-white.svg b/src/assets/wallet-icons/enkrypt-white.svg new file mode 100644 index 000000000..29821e3fd --- /dev/null +++ b/src/assets/wallet-icons/enkrypt-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/common/components/exchange/ConnectDevice.vue b/src/common/components/exchange/ConnectDevice.vue index 162d018f2..ba5893ca5 100644 --- a/src/common/components/exchange/ConnectDevice.vue +++ b/src/common/components/exchange/ConnectDevice.vue @@ -118,6 +118,10 @@ export default defineComponent({ // eslint-disable-next-line global-require, import/no-dynamic-require return require('@/assets/exchange/leather/connect_leather.png'); } + if (bitcoinWallet.value === constants.WALLET_NAMES.ENKRYPT.long_name) { + // eslint-disable-next-line global-require, import/no-dynamic-require + return require('@/assets/exchange/enkrypt/connect_enkrypt.png'); + } // eslint-disable-next-line global-require, import/no-dynamic-require return require('@/assets/exchange/wallet.png'); }); diff --git a/src/common/components/exchange/SelectBitcoinWallet.vue b/src/common/components/exchange/SelectBitcoinWallet.vue index 80c595d14..ba9a09bc6 100644 --- a/src/common/components/exchange/SelectBitcoinWallet.vue +++ b/src/common/components/exchange/SelectBitcoinWallet.vue @@ -92,6 +92,9 @@ export default { case constants.WALLET_NAMES.XVERSE.long_name: wallet = constants.WALLET_NAMES.XVERSE.short_name; break; + case constants.WALLET_NAMES.ENKRYPT.long_name: + wallet = constants.WALLET_NAMES.ENKRYPT.short_name; + break; default: wallet = ''; break; diff --git a/src/common/services/EnkryptService.ts b/src/common/services/EnkryptService.ts new file mode 100644 index 000000000..6f8d20830 --- /dev/null +++ b/src/common/services/EnkryptService.ts @@ -0,0 +1,91 @@ +/* eslint-disable class-methods-use-this, @typescript-eslint/no-explicit-any */ +import * as bitcoin from 'bitcoinjs-lib'; +import { + BtcAccount, + WalletAddress, + SignedTx, +} from '@/common/types'; +import { WalletService } from '@/common/services/index'; +import { EnkryptTx } from '@/pegin/middleware/TxBuilder/EnkryptTxBuilder'; +import * as constants from '@/common/store/constants'; +import ProviderError from '@enkryptcom/types'; + +export default class EnkryptService extends WalletService { + private btcProvider; + + constructor() { + super(); + if (this.network === 'test') window.enkrypt.providers.bitcoin.switchNetwork('testnet'); + this.btcProvider = window.enkrypt.providers.bitcoin; + } + + name(): Record<'formal_name' | 'short_name' | 'long_name', string> { + return constants.WALLET_NAMES.ENKRYPT; + } + + getAccountAddresses(): Promise { + return new Promise((resolve, reject) => { + this.btcProvider.getAccounts() + .then((addresses: string[]) => { + const walletAddresses = addresses + .map((address) => ({ address, derivationPath: '', publicKey: '' } as WalletAddress)); + resolve(walletAddresses); + }) + .catch((e: typeof ProviderError) => reject(e)); + }); + } + + availableAccounts(): BtcAccount[] { + return [constants.BITCOIN_NATIVE_SEGWIT_ADDRESS]; + } + + isConnected(): Promise { + return new Promise((resolve) => { + resolve(this.btcProvider.isConnected()); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getXpub(accountType: BtcAccount, accountNumber: number): Promise { + return Promise.reject(new Error()); + } + + /** Needed to load account balance */ + executed = false; + + areEnoughUnusedAddresses(): boolean { + if (!this.executed) { + this.executed = true; + return !this.executed; + } + return this.executed; + } + + sign(tx: EnkryptTx): Promise { + return new Promise((resolve, reject) => { + this.btcProvider?.signPsbt(tx.hex, { autoFinalized: false }) + .then((hex: string) => { + const signedPsbt = bitcoin.Psbt.fromHex(hex) + .finalizeAllInputs() + .extractTransaction() + .toHex(); + resolve({ signedTx: signedPsbt }); + }) + .catch((e: typeof ProviderError) => { + reject(e); + }); + }); + } + + async reconnect(): Promise { + return new Promise((resolve, reject) => { + try { + if (this.network === 'test') window.enkrypt.providers.bitcoin.switchNetwork('testnet'); + this.btcProvider = window.enkrypt.providers.bitcoin; + resolve(); + } catch (e: any) { + reject(e); + } + }); + } +} diff --git a/src/common/services/LeatherService.ts b/src/common/services/LeatherService.ts index 21979dcb2..ff5ed8343 100644 --- a/src/common/services/LeatherService.ts +++ b/src/common/services/LeatherService.ts @@ -2,7 +2,6 @@ import * as bitcoin from 'bitcoinjs-lib'; import { BtcAccount, WalletAddress, - Step, SignedTx, } from '@/common/types'; import { WalletService } from '@/common/services/index'; @@ -40,32 +39,6 @@ export default class LeatherService extends WalletService { ]; } - // eslint-disable-next-line class-methods-use-this - confirmationSteps(): Step[] { - return [ - { - title: 'Transaction information', - subtitle: '', - outputsToshow: { - opReturn: { - value: false, - amount: true, - }, - change: { - address: false, - amount: true, - }, - federation: { - address: true, - amount: true, - }, - }, - fee: true, - fullAmount: false, - }, - ]; - } - // eslint-disable-next-line class-methods-use-this isConnected(): Promise { return new Promise((resolve) => { diff --git a/src/common/services/LedgerService.ts b/src/common/services/LedgerService.ts index 24b0ba8f6..0c1f5bac6 100644 --- a/src/common/services/LedgerService.ts +++ b/src/common/services/LedgerService.ts @@ -4,7 +4,7 @@ import * as bitcoin from 'bitcoinjs-lib'; import * as constants from '@/common/store/constants'; import { BtcAccount, WalletAddress } from '@/common/types/pegInTx'; import { - LedgerjsTransaction, LedgerSignedTx, LedgerTx, Step, Tx, + LedgerjsTransaction, LedgerSignedTx, LedgerTx, Tx, } from '@/common/types'; import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; import { WalletService } from '@/common/services/index'; @@ -33,92 +33,6 @@ export default class LedgerService extends WalletService { return constants.WALLET_NAMES.LEDGER; } - // eslint-disable-next-line class-methods-use-this - confirmationSteps(): Step[] { - return [ - { - title: 'Confirm transaction', - subtitle: 'Please check your Ledger device', - outputsToshow: { - opReturn: { - value: true, - amount: true, - }, - change: { - address: false, - amount: false, - }, - federation: { - address: false, - amount: false, - }, - }, - fee: false, - fullAmount: false, - }, - { - title: 'Confirm funds transfer', - subtitle: 'Confirm sending', - outputsToshow: { - opReturn: { - value: false, - amount: false, - }, - change: { - address: false, - amount: false, - }, - federation: { - address: true, - amount: true, - }, - }, - fee: false, - fullAmount: false, - }, - { - title: 'Confirm change address', - subtitle: 'Confirm sending', - outputsToshow: { - opReturn: { - value: false, - amount: false, - }, - change: { - address: true, - amount: true, - }, - federation: { - address: false, - amount: false, - }, - }, - fee: false, - fullAmount: false, - }, - { - title: 'Confirm transaction fee', - subtitle: 'Confirm transaction', - outputsToshow: { - opReturn: { - value: false, - amount: false, - }, - change: { - address: false, - amount: false, - }, - federation: { - address: false, - amount: false, - }, - }, - fee: true, - fullAmount: false, - }, - ]; - } - // eslint-disable-next-line class-methods-use-this public reconnect(): Promise { return new Promise((resolve, reject) => { diff --git a/src/common/services/TrezorService.ts b/src/common/services/TrezorService.ts index 0f7d6d4f5..d711aa1da 100644 --- a/src/common/services/TrezorService.ts +++ b/src/common/services/TrezorService.ts @@ -6,7 +6,6 @@ import * as constants from '@/common/store/constants'; import { GetAddress, SignedTx, - Step, TrezorTx, Tx, } from '@/common/types'; import { WalletService } from '@/common/services/index'; @@ -64,92 +63,6 @@ export default class TrezorService extends WalletService { ]; } - // eslint-disable-next-line class-methods-use-this - confirmationSteps(): Step[] { - return [ - { - title: 'Confirm transaction', - subtitle: 'Please check your Trezor device', - outputsToshow: { - opReturn: { - value: true, - amount: true, - }, - change: { - address: false, - amount: false, - }, - federation: { - address: false, - amount: false, - }, - }, - fee: false, - fullAmount: false, - }, - { - title: 'Confirm funds transfer', - subtitle: 'Confirm sending', - outputsToshow: { - opReturn: { - value: false, - amount: false, - }, - change: { - address: false, - amount: false, - }, - federation: { - address: true, - amount: true, - }, - }, - fee: false, - fullAmount: false, - }, - { - title: 'Confirm change address', - subtitle: 'Confirm sending', - outputsToshow: { - opReturn: { - value: false, - amount: false, - }, - change: { - address: true, - amount: true, - }, - federation: { - address: false, - amount: false, - }, - }, - fee: false, - fullAmount: false, - }, - { - title: 'Confirm transaction fee', - subtitle: 'Really send', - outputsToshow: { - opReturn: { - value: false, - amount: false, - }, - change: { - address: false, - amount: false, - }, - federation: { - address: false, - amount: false, - }, - }, - fee: true, - fullAmount: true, - }, - ]; - } - // eslint-disable-next-line class-methods-use-this isConnected(): Promise { return new Promise((resolve) => { diff --git a/src/common/services/WalletService.ts b/src/common/services/WalletService.ts index fc0bced3d..39169541b 100644 --- a/src/common/services/WalletService.ts +++ b/src/common/services/WalletService.ts @@ -1,7 +1,7 @@ import * as constants from '@/common/store/constants'; import SatoshiBig from '@/common/types/SatoshiBig'; import { - Purpose, SignedTx, WalletCount, Step, + Purpose, SignedTx, WalletCount, } from '@/common/types/Wallets'; import { AccountBalance, AddressStatus, AppNetwork, BtcAccount, Tx, UtxoListPerAccount, WalletAddress, @@ -91,8 +91,6 @@ export default abstract class WalletService { abstract availableAccounts(): Array; - abstract confirmationSteps(): Array; - get isLoadingBalances(): boolean { return this.loadingBalances; } diff --git a/src/common/services/index.ts b/src/common/services/index.ts index be5b73a3a..91e111e62 100644 --- a/src/common/services/index.ts +++ b/src/common/services/index.ts @@ -5,3 +5,4 @@ export { default as LedgerService } from './LedgerService'; export { default as LeatherService } from './LeatherService'; export { default as FlyoverService } from './FlyoverService'; export { default as XverseService } from './XverseService'; +export { default as EnkryptService } from './EnkryptService'; diff --git a/src/common/store/constants.ts b/src/common/store/constants.ts index c4b4ec06d..3c33d12bb 100644 --- a/src/common/store/constants.ts +++ b/src/common/store/constants.ts @@ -4,6 +4,7 @@ export const WALLET_NAMES = { METAMASK: { formal_name: 'Metamask', short_name: 'metamask', long_name: 'WALLET_METAMASK' }, LEATHER: { formal_name: 'Leather', short_name: 'leather', long_name: 'WALLET_LEATHER' }, XVERSE: { formal_name: 'XVerse', short_name: 'xverse', long_name: 'WALLET_XVERSE' }, + ENKRYPT: { formal_name: 'Enkrypt', short_name: 'enkrypt', long_name: 'WALLET_ENKRYPT' }, } as const; export const OPERATION_TYPE = 'OPERATION_TYPE'; diff --git a/src/common/types/pegInTx.ts b/src/common/types/pegInTx.ts index a35980eec..6e2666550 100644 --- a/src/common/types/pegInTx.ts +++ b/src/common/types/pegInTx.ts @@ -7,7 +7,7 @@ export type BtcAccount = 'BITCOIN_LEGACY_ADDRESS' | 'BITCOIN_SEGWIT_ADDRESS' | 'BITCOIN_NATIVE_SEGWIT_ADDRESS'; -export type BtcWallet = 'WALLET_LEDGER' | 'WALLET_TREZOR' | 'WALLET_LEATHER' | 'WALLET_XVERSE'; +export type BtcWallet = 'WALLET_LEDGER' | 'WALLET_TREZOR' | 'WALLET_LEATHER' | 'WALLET_XVERSE' | 'WALLET_ENKRYPT'; export type MiningSpeedFee = 'BITCOIN_SLOW_FEE_LEVEL' | 'BITCOIN_AVERAGE_FEE_LEVEL' | diff --git a/src/common/walletConf.json b/src/common/walletConf.json index 390f6b0d9..660620d9c 100644 --- a/src/common/walletConf.json +++ b/src/common/walletConf.json @@ -47,6 +47,18 @@ "btnClass": "btn-xverse", "kind": "Software Wallet", "installation": "https://www.xverse.app/download" + }, + { + "name": "Enkrypt", + "icon": "wallet-icons/enkrypt-black.svg", + "iconWhite": "wallet-icons/enkrypt-white.svg", + "constant": "WALLET_ENKRYPT", + "pegin": true, + "pegout": false, + "hover": false, + "btnClass": "btn-enkrypt", + "kind": "Software Wallet", + "installation": "https://www.enkrypt.com/" } ] } diff --git a/src/pegin/components/create/PegInAccountSelect.vue b/src/pegin/components/create/PegInAccountSelect.vue index f4fa9726b..5ee9a1ea6 100644 --- a/src/pegin/components/create/PegInAccountSelect.vue +++ b/src/pegin/components/create/PegInAccountSelect.vue @@ -126,6 +126,7 @@ export default defineComponent({ const onlyNativeSegwit = computed(() => { const wallets = [ constants.WALLET_NAMES.LEATHER.long_name, + constants.WALLET_NAMES.ENKRYPT.long_name, ]; return wallets.some((wallet) => wallet === bitcoinWallet.value); }); diff --git a/src/pegin/components/create/SendBitcoin.vue b/src/pegin/components/create/SendBitcoin.vue index 31a0531e5..b1fc6e07e 100644 --- a/src/pegin/components/create/SendBitcoin.vue +++ b/src/pegin/components/create/SendBitcoin.vue @@ -50,6 +50,7 @@ import ConnectDevice from '@/common/components/exchange/ConnectDevice.vue'; import TxErrorDialog from '@/common/components/exchange/TxErrorDialog.vue'; import { TrezorError } from '@/common/types/exception/TrezorError'; import LeatherTxBuilder from '@/pegin/middleware/TxBuilder/LeatherTxBuilder'; +import EnkryptTxBuilder from '@/pegin/middleware/TxBuilder/EnkryptTxBuilder'; import PeginTxService from '@/pegin/services/PeginTxService'; import XverseTxBuilder from '@/pegin/middleware/TxBuilder/XverseTxBuilder'; @@ -198,6 +199,10 @@ export default defineComponent({ txBuilder.value = new XverseTxBuilder(); currentWallet.value = constants.WALLET_NAMES.XVERSE.short_name; break; + case constants.WALLET_NAMES.ENKRYPT.long_name: + txBuilder.value = new EnkryptTxBuilder(); + currentWallet.value = constants.WALLET_NAMES.ENKRYPT.short_name; + break; default: txBuilder.value = new TrezorTxBuilder(); break; diff --git a/src/pegin/middleware/TxBuilder/EnkryptTxBuilder.ts b/src/pegin/middleware/TxBuilder/EnkryptTxBuilder.ts new file mode 100644 index 000000000..32c7fb621 --- /dev/null +++ b/src/pegin/middleware/TxBuilder/EnkryptTxBuilder.ts @@ -0,0 +1,80 @@ +/* eslint-disable class-methods-use-this */ +import { ApiService } from '@/common/services'; +import { + Tx, + NormalizedTx, + NormalizedInput, + PsbtExtendedInput, +} from '@/common/types'; +import * as bitcoin from 'bitcoinjs-lib'; +import TxBuilder from './TxBuilder'; + +export interface EnkryptTx extends Tx { + hex: string; +} + +export default class EnkryptTxBuilder extends TxBuilder { + buildTx(normalizedTx: NormalizedTx): Promise { + return new Promise((resolve, reject) => { + const psbt = new bitcoin.Psbt({ network: this.network }); + EnkryptTxBuilder.getExtendedInputs(normalizedTx.inputs) + .then((extendedInputs) => { + psbt.addInputs(extendedInputs); + normalizedTx.outputs.forEach((normalizedOutput) => { + if (normalizedOutput.op_return_data) { + const buffer = Buffer.from(normalizedOutput.op_return_data, 'hex'); + const script: bitcoin.Payment = bitcoin.payments.embed({ data: [buffer] }); + if (script.output) { + psbt.addOutput({ + script: script.output, + value: 0, + }); + } + } else if (normalizedOutput.address) { + psbt.addOutput({ + address: normalizedOutput.address, + value: Number(normalizedOutput.amount), + }); + } + }); + const inputs = normalizedTx.inputs + .map((input) => ({ + address: input.address, + idx: input.prev_index, + })); + resolve({ + coin: this.coin, + inputs, + outputs: normalizedTx.outputs, + hex: psbt.toHex(), + }); + }) + .catch(reject); + }); + } + + private static getExtendedInputs(normalizedInputs: Array) + :Promise> { + return new Promise>((resolve, reject) => { + const psbtExtendedInputs: Array = []; + const hexUtxoPromises = normalizedInputs + .map((input) => ApiService.getTxHex(input.prev_hash)); + Promise.all(hexUtxoPromises) + .then((hexUtxos) => { + normalizedInputs.forEach((normalizedInput, idx) => { + const utxo = bitcoin.Transaction.fromHex(hexUtxos[idx]); + psbtExtendedInputs.push({ + hash: normalizedInput.prev_hash, + index: normalizedInput.prev_index, + witnessUtxo: { + value: utxo.outs[normalizedInput.prev_index].value, + script: utxo.outs[normalizedInput.prev_index].script, + }, + }); + }); + resolve(psbtExtendedInputs); + }) + .catch(reject); + }); + } +} diff --git a/src/pegin/store/PeginTx/actions.ts b/src/pegin/store/PeginTx/actions.ts index 69e2e1813..3c188ec87 100644 --- a/src/pegin/store/PeginTx/actions.ts +++ b/src/pegin/store/PeginTx/actions.ts @@ -4,7 +4,8 @@ import * as rskUtils from '@rsksmart/rsk-utils'; import * as constants from '@/common/store/constants'; import { ApiService, LedgerService, - TrezorService, LeatherService, XverseService, + TrezorService, LeatherService, + EnkryptService, XverseService, } from '@/common/services'; import SatoshiBig from '@/common/types/SatoshiBig'; import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; @@ -48,6 +49,9 @@ export const actions: ActionTree = { case constants.WALLET_NAMES.XVERSE.long_name: commit(constants.PEGIN_TX_SET_WALLET_SERVICE, new XverseService()); break; + case constants.WALLET_NAMES.ENKRYPT.long_name: + commit(constants.PEGIN_TX_SET_WALLET_SERVICE, new EnkryptService()); + break; default: commit(constants.PEGIN_TX_SET_WALLET_SERVICE, undefined); break; diff --git a/src/pegin/store/PeginTx/getters.ts b/src/pegin/store/PeginTx/getters.ts index d46e6080c..e2177c9bf 100644 --- a/src/pegin/store/PeginTx/getters.ts +++ b/src/pegin/store/PeginTx/getters.ts @@ -20,6 +20,9 @@ export const getters: GetterTree = { case constants.WALLET_NAMES.XVERSE.long_name: { return constants.WALLET_NAMES.XVERSE.formal_name; } + case constants.WALLET_NAMES.ENKRYPT.long_name: { + return constants.WALLET_NAMES.ENKRYPT.formal_name; + } default: { return 'wallet'; } @@ -194,6 +197,9 @@ export const getters: GetterTree = { case constants.WALLET_NAMES.XVERSE.long_name: isSfWallet = true; break; + case constants.WALLET_NAMES.ENKRYPT.long_name: + isSfWallet = true; + break; default: isSfWallet = false; break; diff --git a/src/pegout/components/FlyoverPegout.vue b/src/pegout/components/FlyoverPegout.vue index 55ae624d4..bf5885a8a 100644 --- a/src/pegout/components/FlyoverPegout.vue +++ b/src/pegout/components/FlyoverPegout.vue @@ -306,7 +306,6 @@ export default defineComponent({ })); async function send() { - console.log('Sending pegout'); const quoteHash = selectedOption.value; const type = quoteHash ? TxStatusType.FLYOVER_PEGOUT.toLowerCase() diff --git a/src/shims-tsx.d.ts b/src/shims-tsx.d.ts index 6a9387bef..90646609d 100644 --- a/src/shims-tsx.d.ts +++ b/src/shims-tsx.d.ts @@ -1,5 +1,6 @@ import Vue, { VNode } from 'vue'; import { LeatherProvider } from '@leather.io/rpc'; +import EnkryptWindow from '@enkryptcom/types'; declare global { namespace JSX { @@ -21,6 +22,7 @@ declare global { interface Window { ethereum: Ethereum, LeatherProvider?: LeatherProvider, + enkrypt?: EnkryptWindow, grecaptcha: { ready: (cb: () => void) => void, execute: () => Promise, @@ -28,4 +30,7 @@ declare global { }, onRecaptchaSuccess: () => Promise, } + interface EnkryptProvider { + getAccounts(): Promise; + } } diff --git a/tests/unit/SatoshiBig.spec.ts b/tests/unit/SatoshiBig.spec.ts index bb18a811b..71d8b8e07 100644 --- a/tests/unit/SatoshiBig.spec.ts +++ b/tests/unit/SatoshiBig.spec.ts @@ -58,7 +58,7 @@ describe('SatoshiBig', () => { expect(sb1.toBTCString()).toEqual('0.00000000'); expect(sb1.toBTCStringNotZeroPadded()).toEqual('0'); }); - + it('should return an instance of SatoshiBig from a WeiBig instance rounded up', () => { const weiToTest = new WeiBig('5301364444000000', 'wei'); const weiToTest2 = new WeiBig('8101341211956000', 'wei'); diff --git a/tests/unit/common/services/EnkryptService.spec.ts b/tests/unit/common/services/EnkryptService.spec.ts new file mode 100644 index 000000000..d0d8a18a8 --- /dev/null +++ b/tests/unit/common/services/EnkryptService.spec.ts @@ -0,0 +1,101 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import sinon from 'sinon'; +import * as constants from '@/common/store/constants'; +import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; +import { EnkryptService, WalletService } from '@/common/services'; + +function setEnvironment() { + const defaultEnvironmentVariables = { + vueAppCoin: constants.BTC_NETWORK_TESTNET, + vueAppRskNodeHost: '', + vueAppApiBaseUrl: 'https://2wp-api.testnet.rsk.co', + }; + EnvironmentAccessorService.initializeEnvironmentVariables(defaultEnvironmentVariables); +} +describe('Enkrypt Service: ', () => { + let mockBitcoinProvider: any; + let enkryptService: EnkryptService; + + beforeEach(() => { + setEnvironment(); + mockBitcoinProvider = { + switchNetwork: jest.fn(), + getAccounts: jest.fn().mockResolvedValue(['testAddress']), + isConnected: jest.fn().mockResolvedValue(true), + signPsbt: jest.fn().mockResolvedValue('70736274ff0100960200000001617bfa33c63e4d6c1dff9f90b116371b298a8f08084802a85bb5db956e8c96a60000000000ffffffff0300000000000000001b6a1952534b5401cd3fb9fdd6035e3da5a997efe5b3d895cbc39ed120a107000000000017a9143b004aa2b568c97f80ccccc5130226d0e98bd588872246010000000000160014dfc8d4318aea9a5a945be8e2eba3cafb94fe2569000000000001011fc027090000000000160014dfc8d4318aea9a5a945be8e2eba3cafb94fe2569220203ad6e44f7982ddcd5626539da854a1337b85be787248d42f9d7a638991464494d47304402205d735b7c295fd0d200c2824b504dc71dd54a45f9e2c43a782b3f67e8b759f48402207937eabfd56d9d177e3dd187ebd3e219eb7c44758f9a05cddd46d35d01e712be0100000000'), + }; + (window as any).enkrypt = { + providers: { + bitcoin: mockBitcoinProvider, + }, + }; + enkryptService = new EnkryptService(); + }); + afterEach(() => { + jest.restoreAllMocks(); + sinon.restore(); + }); + it('should create a EnkryptService instance', () => { + expect(enkryptService).toBeInstanceOf(WalletService); + expect(enkryptService).toBeInstanceOf(EnkryptService); + expect(mockBitcoinProvider.switchNetwork).toHaveBeenCalledWith('testnet'); + }); + it('should return the wallet name', () => { + expect(enkryptService.name()).toEqual(constants.WALLET_NAMES.ENKRYPT); + }); + it('should return native segwit as available accounts', () => { + expect(enkryptService.availableAccounts()[0]).toEqual(constants.BITCOIN_NATIVE_SEGWIT_ADDRESS); + }); + it('should return a single wallet address', () => { + enkryptService.getAccountAddresses().then((addresses) => { + expect(addresses.length).toBe(1); + expect(addresses[0].address).toBe('testAddress'); + }); + }); + it('should return an error when getXpub is called', () => { + enkryptService.getXpub(constants.BITCOIN_NATIVE_SEGWIT_ADDRESS, 0).catch((e) => { + expect(e).toBeInstanceOf(Error); + }); + }); + it('should return a boolean if there are enough unused addresses', () => { + expect(enkryptService.areEnoughUnusedAddresses()).toBe(false); + expect(enkryptService.areEnoughUnusedAddresses()).toBe(true); + }); + it('should return native segwit as available accounts', () => { + expect(enkryptService.availableAccounts()[0]).toEqual(constants.BITCOIN_NATIVE_SEGWIT_ADDRESS); + }); + it('should return true if enkrypt is connected', () => { + enkryptService.isConnected().then((isConnected) => { + expect(isConnected).toBe(true); + }); + }); + it('should return a sign psbt', () => { + const tx = { + coin: 'test', + inputs: [ + { + address: 'tb1qmlydgvv2a2d949zmar3whg72lw20uftftg4vr8', + idx: 0, + }, + ], + outputs: [ + { + amount: '0', + op_return_data: '52534b5401cd3Fb9fdd6035E3dA5A997EfE5b3D895CbC39ed1', + }, + { + address: '2MxdCCrmUaEG1Tk8dshdcTGKiA9LewNDVCb', + amount: '500000', + }, + { + address: 'tb1qmlydgvv2a2d949zmar3whg72lw20uftftg4vr8', + amount: '83490', + }, + ], + hex: '70736274ff0100960200000001617bfa33c63e4d6c1dff9f90b116371b298a8f08084802a85bb5db956e8c96a60000000000ffffffff0300000000000000001b6a1952534b5401cd3fb9fdd6035e3da5a997efe5b3d895cbc39ed120a107000000000017a9143b004aa2b568c97f80ccccc5130226d0e98bd588872246010000000000160014dfc8d4318aea9a5a945be8e2eba3cafb94fe2569000000000001011fc027090000000000160014dfc8d4318aea9a5a945be8e2eba3cafb94fe256900000000', + }; + enkryptService.sign(tx).then((res) => { + expect(res.signedTx).toEqual('02000000000101617bfa33c63e4d6c1dff9f90b116371b298a8f08084802a85bb5db956e8c96a60000000000ffffffff0300000000000000001b6a1952534b5401cd3fb9fdd6035e3da5a997efe5b3d895cbc39ed120a107000000000017a9143b004aa2b568c97f80ccccc5130226d0e98bd588872246010000000000160014dfc8d4318aea9a5a945be8e2eba3cafb94fe25690247304402205d735b7c295fd0d200c2824b504dc71dd54a45f9e2c43a782b3f67e8b759f48402207937eabfd56d9d177e3dd187ebd3e219eb7c44758f9a05cddd46d35d01e712be012103ad6e44f7982ddcd5626539da854a1337b85be787248d42f9d7a638991464494d00000000'); + }); + }); +}); diff --git a/tests/unit/pegin/services/EnkryptTxBuilder.spec.ts b/tests/unit/pegin/services/EnkryptTxBuilder.spec.ts new file mode 100644 index 000000000..f55a991bc --- /dev/null +++ b/tests/unit/pegin/services/EnkryptTxBuilder.spec.ts @@ -0,0 +1,67 @@ +import { NormalizedTx } from '@/common/types'; +import EnkryptTxBuilder from '@/pegin/middleware/TxBuilder/EnkryptTxBuilder'; +import * as constants from '@/common/store/constants'; +import ApiService from '@/common/services/ApiService'; +import { EnvironmentAccessorService } from '@/common/services/enviroment-accessor.service'; +import sinon from 'sinon'; + +describe('EnkryptTxBuilder', () => { + let enkryptTxBuilder: EnkryptTxBuilder; + + function setEnvironment() { + const defaultEnvironmentVariables = { + vueAppCoin: constants.BTC_NETWORK_TESTNET, + vueAppRskNodeHost: '', + vueAppApiBaseUrl: 'https://2wp-api.testnet.rsk.co', + }; + EnvironmentAccessorService.initializeEnvironmentVariables(defaultEnvironmentVariables); + } + + beforeEach(() => { + setEnvironment(); + enkryptTxBuilder = new EnkryptTxBuilder(); + sinon.stub(ApiService, 'getTxHex') + .resolves('020000000001010366d8862273dc1afe60c26d3449ae248a7e15a50f97242d57fd9dcaeca5d96400000000000000000002c027090000000000160014dfc8d4318aea9a5a945be8e2eba3cafb94fe256975910500000000001600149b6d476d887db413ed0a59fbb1ea80ed41641e70024730440220535da8f53c535ed68b5a30b2adc87e424467351f2e867ae0ee4472668ef3a20a02202d1713a3bbed41e26b9fd19f08cdf665fdeba00b4295f858daa5ab69f1457ae401210296b60d2b92e4ba3f1948e00412d5fdc4ec0586830660c806ffe2214daa25fce900000000'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('builds a transaction', async () => { + const inputs = [ + { + address: 'tb1qmlydgvv2a2d949zmar3whg72lw20uftftg4vr8', + amount: '600000', + prev_hash: 'd4e1c03847c3c732893453704f94659501a2e7e37389d2d72cc562d00ca0eff1', + prev_index: 0, + }, + ]; + const outputs = [ + { + amount: '0', + op_return_data: '52534b5401cd3Fb9fdd6035E3dA5A997EfE5b3D895CbC39ed1', + }, + { + address: '2MxdCCrmUaEG1Tk8dshdcTGKiA9LewNDVCb', + amount: '500000', + }, + { + address: 'tb1qmlydgvv2a2d949zmar3whg72lw20uftftg4vr8', + amount: '74346', + }, + ]; + const normalizedTx: NormalizedTx = { + inputs, + outputs, + coin: constants.BTC_NETWORK_TESTNET, + }; + expect(normalizedTx.coin).toBe(constants.BTC_NETWORK_TESTNET); + + const tx = await enkryptTxBuilder.buildTx(normalizedTx); + + expect(tx.coin).toBe(constants.BTC_NETWORK_TESTNET); + expect(tx.inputs.length).toEqual(inputs.length); + expect(tx.outputs.length).toEqual(outputs.length); + }); +}); From 50ecc6699c3d1026c28b77a22f41ec220a4fcd43 Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Thu, 26 Sep 2024 17:21:09 -0300 Subject: [PATCH 017/104] Fix top balance format Prevents incorrect display of balances for low values expressed in exponential notation --- src/common/components/layouts/Top.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/components/layouts/Top.vue b/src/common/components/layouts/Top.vue index eba03d728..f9b8adb91 100644 --- a/src/common/components/layouts/Top.vue +++ b/src/common/components/layouts/Top.vue @@ -16,7 +16,7 @@ {{ balance.toRBTCTrimmedString() }} {{ environmentContext.getRbtcTicker() }} + >{{ balance.toRBTCString() }} {{ environmentContext.getRbtcTicker() }} { - const amount = balance.value.toRBTCTrimmedString().slice(0, 7); + const amount = balance.value.toRBTCString().slice(0, 7); return `${amount} ${environmentContext.getRbtcTicker()}`; }); From cdc118b8d43e5a9370464279736b4484e48abe2a Mon Sep 17 00:00:00 2001 From: Anni Piragauta Date: Wed, 25 Sep 2024 11:09:14 -0500 Subject: [PATCH 018/104] refactor: remove pegin modal steps --- .../components/exchange/BtcToRbtcDialog.vue | 97 ------------------- src/pegin/views/PegIn.vue | 22 +---- 2 files changed, 1 insertion(+), 118 deletions(-) delete mode 100644 src/common/components/exchange/BtcToRbtcDialog.vue diff --git a/src/common/components/exchange/BtcToRbtcDialog.vue b/src/common/components/exchange/BtcToRbtcDialog.vue deleted file mode 100644 index 1c08edf40..000000000 --- a/src/common/components/exchange/BtcToRbtcDialog.vue +++ /dev/null @@ -1,97 +0,0 @@ - - - - - diff --git a/src/pegin/views/PegIn.vue b/src/pegin/views/PegIn.vue index d934d4b35..f412af21f 100644 --- a/src/pegin/views/PegIn.vue +++ b/src/pegin/views/PegIn.vue @@ -1,16 +1,12 @@ From 29ed03c71438500fc5d5fe66c9d7fecd5fbdf68e Mon Sep 17 00:00:00 2001 From: Anni Piragauta Date: Tue, 8 Oct 2024 13:53:47 -0500 Subject: [PATCH 019/104] fix: refund address to enable flyover option for enkrypt and xverse --- src/pegin/store/PeginTx/getters.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pegin/store/PeginTx/getters.ts b/src/pegin/store/PeginTx/getters.ts index e2177c9bf..c60a4b9b1 100644 --- a/src/pegin/store/PeginTx/getters.ts +++ b/src/pegin/store/PeginTx/getters.ts @@ -67,7 +67,10 @@ export const getters: GetterTree = { const coin = EnvironmentAccessorService.getEnvironmentVariables().vueAppCoin; if (currentView && currentView === 'Status') { address = state.statusInfo.refundAddress; - } else if (localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.LEATHER.formal_name) { + } else if (localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.LEATHER.formal_name + || localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.XVERSE.formal_name + || localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.ENKRYPT.formal_name + ) { address = coin === 'main' ? constants.VALID_ADDRESS_UNUSED_BY_FLYOVER.mainnet : constants.VALID_ADDRESS_UNUSED_BY_FLYOVER.testnet; From e2c2644acdce44b387dae60d4869f1b7098ae742 Mon Sep 17 00:00:00 2001 From: ronaldsg Date: Mon, 7 Oct 2024 20:03:29 -0500 Subject: [PATCH 020/104] Add block event listener to refresh balance --- src/common/store/session/actions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/store/session/actions.ts b/src/common/store/session/actions.ts index cd2cb481e..391d81f99 100644 --- a/src/common/store/session/actions.ts +++ b/src/common/store/session/actions.ts @@ -28,6 +28,7 @@ export const actions: ActionTree = { commit(constants.SESSION_SET_RLOGIN, rLoginResponse); commit(constants.SESSION_SET_RLOGIN_INSTANCE, rLogin); commit(constants.SESSION_SET_WEB3_INSTANCE, markRaw(provider)); + provider.on('block', () => dispatch(constants.WEB3_SESSION_ADD_BALANCE)); return provider.listAccounts(); }) .then((accounts) => { From 86e797a3df258bff765ca17ab52740bb137eab20 Mon Sep 17 00:00:00 2001 From: ronaldsg Date: Fri, 11 Oct 2024 00:56:19 -0500 Subject: [PATCH 021/104] Refactor: PeginQuote class definition I've defined the PeginQUote class in order to assing all the required behavior to each specific quote object --- src/common/services/FlyoverService.ts | 19 ++---- src/common/types/{ => Flyover}/Flyover.ts | 4 +- .../types/{ => Flyover}/FlyoverPegin.ts | 11 ++-- .../types/{ => Flyover}/FlyoverPegout.ts | 9 ++- src/common/types/Flyover/PeginQuote.ts | 34 ++++++++++ src/common/types/Flyover/index.ts | 4 ++ src/common/types/index.ts | 2 - src/common/types/store.ts | 4 +- src/pegin/components/create/ConfirmTx.vue | 15 ++--- .../components/create/ConfirmationSteps.vue | 12 +--- src/pegin/components/create/PegInForm.vue | 20 ++---- .../components/create/PeginOptionCard.vue | 20 +++--- src/pegin/store/FlyoverPegin/mutations.ts | 4 +- tests/unit/common/PeginQuote.spec.ts | 66 +++++++++++++++++++ 14 files changed, 147 insertions(+), 77 deletions(-) rename src/common/types/{ => Flyover}/Flyover.ts (96%) rename src/common/types/{ => Flyover}/FlyoverPegin.ts (54%) rename src/common/types/{ => Flyover}/FlyoverPegout.ts (62%) create mode 100644 src/common/types/Flyover/PeginQuote.ts create mode 100644 src/common/types/Flyover/index.ts create mode 100644 tests/unit/common/PeginQuote.spec.ts diff --git a/src/common/services/FlyoverService.ts b/src/common/services/FlyoverService.ts index c22f811aa..8b909f853 100644 --- a/src/common/services/FlyoverService.ts +++ b/src/common/services/FlyoverService.ts @@ -7,7 +7,7 @@ import { } from '@rsksmart/flyover-sdk'; import * as constants from '@/common/store/constants'; import { - LiquidityProvider2WP, QuotePegIn2WP, QuotePegOut2WP, + LiquidityProvider2WP, PeginQuote, QuotePegOut2WP, SatoshiBig, WeiBig, } from '@/common/types'; import { providers } from 'ethers'; @@ -317,8 +317,8 @@ export default class FlyoverService { rootstockRecipientAddress: string, bitcoinRefundAddress: string, valueToTransfer: SatoshiBig, - ):Promise> { - return new Promise>((resolve, reject) => { + ):Promise> { + return new Promise>((resolve, reject) => { this.flyover?.getQuotes({ rskRefundAddress: rootstockRecipientAddress, bitcoinRefundAddress, @@ -335,18 +335,7 @@ export default class FlyoverService { bitcoinRefundAddress, valueToTransfer, })) - .map(({ quote, quoteHash }: Quote) => ({ - quote: { - ...quote, - timeForDepositInSeconds: quote.timeForDeposit, - callFee: SatoshiBig.fromWeiBig(new WeiBig(quote.callFee ?? 0, 'wei')), - gasFee: new WeiBig(quote.gasFee ?? 0, 'wei'), - penaltyFee: new WeiBig(quote.penaltyFee ?? 0, 'wei'), - productFeeAmount: SatoshiBig.fromWeiBig(new WeiBig(quote.productFeeAmount ?? 0, 'wei')), - value: SatoshiBig.fromWeiBig(new WeiBig(quote.value ?? 0, 'wei')), - }, - quoteHash, - })); + .map((quoteFromServer: Quote) => new PeginQuote(quoteFromServer)); resolve(peginQuotes); }) .catch((error: Error) => { diff --git a/src/common/types/Flyover.ts b/src/common/types/Flyover/Flyover.ts similarity index 96% rename from src/common/types/Flyover.ts rename to src/common/types/Flyover/Flyover.ts index 4155652a3..ea031cbfb 100644 --- a/src/common/types/Flyover.ts +++ b/src/common/types/Flyover/Flyover.ts @@ -1,6 +1,6 @@ import { LiquidityProviderBase } from '@rsksmart/flyover-sdk'; -import WeiBig from './WeiBig'; -import SatoshiBig from './SatoshiBig'; +import WeiBig from '../WeiBig'; +import SatoshiBig from '../SatoshiBig'; interface PeginProviderDetail { fee: SatoshiBig; diff --git a/src/common/types/FlyoverPegin.ts b/src/common/types/Flyover/FlyoverPegin.ts similarity index 54% rename from src/common/types/FlyoverPegin.ts rename to src/common/types/Flyover/FlyoverPegin.ts index f28780244..9d5148bed 100644 --- a/src/common/types/FlyoverPegin.ts +++ b/src/common/types/Flyover/FlyoverPegin.ts @@ -1,7 +1,8 @@ -import { FlyoverService } from '../services'; -import { LiquidityProvider2WP, QuotePegIn2WP } from './Flyover'; -import SatoshiBig from './SatoshiBig'; -import WeiBig from './WeiBig'; +import { FlyoverService } from '../../services'; +import { LiquidityProvider2WP } from './Flyover'; +import SatoshiBig from '../SatoshiBig'; +import WeiBig from '../WeiBig'; +import PeginQuote from './PeginQuote'; export interface FlyoverPeginState { amountToTransfer: SatoshiBig; @@ -9,7 +10,7 @@ export interface FlyoverPeginState { rootstockRecipientAddress: string; valueToReceive: WeiBig; liquidityProviders: LiquidityProvider2WP[]; - quotes: Record; + quotes: Record; flyoverService: FlyoverService; txHash?: string; selectedQuoteHash: string; diff --git a/src/common/types/FlyoverPegout.ts b/src/common/types/Flyover/FlyoverPegout.ts similarity index 62% rename from src/common/types/FlyoverPegout.ts rename to src/common/types/Flyover/FlyoverPegout.ts index 6fc056175..2527edada 100644 --- a/src/common/types/FlyoverPegout.ts +++ b/src/common/types/Flyover/FlyoverPegout.ts @@ -1,8 +1,7 @@ -import { ObjectDifference } from '@/common/types'; -import { FlyoverService } from '../services'; -import { LiquidityProvider2WP, QuotePegOut2WP } from './Flyover'; -import SatoshiBig from './SatoshiBig'; -import WeiBig from './WeiBig'; +import { LiquidityProvider2WP, ObjectDifference, QuotePegOut2WP } from '@/common/types'; +import { FlyoverService } from '../../services'; +import SatoshiBig from '../SatoshiBig'; +import WeiBig from '../WeiBig'; export interface FlyoverPegoutState { differences: Array; diff --git a/src/common/types/Flyover/PeginQuote.ts b/src/common/types/Flyover/PeginQuote.ts new file mode 100644 index 000000000..ca93c5eab --- /dev/null +++ b/src/common/types/Flyover/PeginQuote.ts @@ -0,0 +1,34 @@ +import { Quote } from '@rsksmart/flyover-sdk'; +import { PeginQuoteDTO2WP, QuotePegIn2WP } from './Flyover'; +import SatoshiBig from '../SatoshiBig'; +import WeiBig from '../WeiBig'; + +export default class PeginQuote implements QuotePegIn2WP { + quote: PeginQuoteDTO2WP; + + quoteHash: string; + + constructor({ quote, quoteHash }: Quote) { + this.quote = { + ...quote, + timeForDepositInSeconds: quote.timeForDeposit, + callFee: SatoshiBig.fromWeiBig(new WeiBig(quote.callFee ?? 0, 'wei')), + gasFee: new WeiBig(quote.gasFee ?? 0, 'wei'), + penaltyFee: new WeiBig(quote.penaltyFee ?? 0, 'wei'), + productFeeAmount: SatoshiBig.fromWeiBig(new WeiBig(quote.productFeeAmount ?? 0, 'wei')), + value: SatoshiBig.fromWeiBig(new WeiBig(quote.value ?? 0, 'wei')), + }; + this.quoteHash = quoteHash; + } + + get totalValueToTransfer(): SatoshiBig { + return this.quote.value + .plus(this.providerFee); + } + + get providerFee(): SatoshiBig { + return this.quote.productFeeAmount + .plus(this.quote.callFee) + .plus(SatoshiBig.fromWeiBig(this.quote.gasFee)); + } +} diff --git a/src/common/types/Flyover/index.ts b/src/common/types/Flyover/index.ts new file mode 100644 index 000000000..9abf72f74 --- /dev/null +++ b/src/common/types/Flyover/index.ts @@ -0,0 +1,4 @@ +export * from './Flyover'; +export * from './FlyoverPegout'; +export * from './FlyoverPegin'; +export { default as PeginQuote } from './PeginQuote'; diff --git a/src/common/types/index.ts b/src/common/types/index.ts index 7b74d70b2..c61e365e7 100644 --- a/src/common/types/index.ts +++ b/src/common/types/index.ts @@ -13,6 +13,4 @@ export { default as WeiBig } from './WeiBig'; export * from './Feature'; export * from './ethers'; export * from './Flyover'; -export * from './FlyoverPegout'; export * from './TxInfo'; -export * from './FlyoverPegin'; diff --git a/src/common/types/store.ts b/src/common/types/store.ts index 345f1b6c6..fdf29bd3f 100644 --- a/src/common/types/store.ts +++ b/src/common/types/store.ts @@ -4,8 +4,8 @@ import SatoshiBig from '@/common/types/SatoshiBig'; import { PegInTxState } from '@/common/types/pegInTx'; import { SessionState } from '@/common/types/session'; import { PegOutTxState } from '@/common/types/pegOutTx'; -import { FlyoverPeginState } from '@/common/types/FlyoverPegin'; -import { FlyoverPegoutState } from '@/common/types/FlyoverPegout'; +import { FlyoverPeginState } from '@/common/types/Flyover/FlyoverPegin'; +import { FlyoverPegoutState } from '@/common/types/Flyover/FlyoverPegout'; import { StatusState } from '@/common/types/Status'; export interface RootState { diff --git a/src/pegin/components/create/ConfirmTx.vue b/src/pegin/components/create/ConfirmTx.vue index 85c4db015..cb6bbd622 100644 --- a/src/pegin/components/create/ConfirmTx.vue +++ b/src/pegin/components/create/ConfirmTx.vue @@ -50,7 +50,8 @@ import { useState, useGetter, useStateAttribute } from '@/common/store/helper'; import { LiquidityProvider2WP, NormalizedSummary, - PegInTxState, QuotePegIn2WP, SatoshiBig, TxStatusType, TxSummaryOrientation, + PeginQuote, + PegInTxState, SatoshiBig, TxStatusType, TxSummaryOrientation, } from '@/common/types'; import EnvironmentContextProviderService from '@/common/providers/EnvironmentContextProvider'; import { mdiInformation, mdiArrowLeft, mdiArrowRight } from '@mdi/js'; @@ -83,7 +84,7 @@ export default defineComponent({ const safeFee = useGetter('pegInTx', constants.PEGIN_TX_GET_SAFE_TX_FEE); const sessionId = useStateAttribute('pegInTx', 'sessionId'); const isHdWallet = useGetter('pegInTx', constants.PEGIN_TX_IS_HD_WALLET); - const selectedQuote = useGetter('flyoverPegin', constants.FLYOVER_PEGIN_GET_SELECTED_QUOTE); + const selectedQuote = useGetter('flyoverPegin', constants.FLYOVER_PEGIN_GET_SELECTED_QUOTE); const liquidityProviders = useStateAttribute('flyoverPegin', 'liquidityProviders'); const recipientAddress = useStateAttribute('flyoverPegin', 'rootstockRecipientAddress'); const peginType = useStateAttribute('pegInTx', 'peginType'); @@ -131,12 +132,6 @@ export default defineComponent({ return provider?.name ?? ''; } - function getProviderFee(): SatoshiBig { - return selectedQuote.value.quote.productFeeAmount - .plus(selectedQuote.value.quote.callFee) - .plus(SatoshiBig.fromWeiBig(selectedQuote.value.quote.gasFee)); - } - const txPeginSummary = computed((): NormalizedSummary => ({ amountFromString: pegInTxState.value.amountToTransfer.toBTCTrimmedString(), amountReceivedString: pegInTxState.value.amountToTransfer.toBTCTrimmedString(), @@ -146,13 +141,13 @@ export default defineComponent({ senderAddress: pegInTxState.value.normalizedTx.inputs[0].address, })); - const flyoverTotalFee = computed(() => getProviderFee() + const flyoverTotalFee = computed(() => selectedQuote.value.providerFee .plus(safeFee.value)); const flyoverPeginSummary = computed((): NormalizedSummary => ({ amountFromString: selectedQuote.value.quote.value.toBTCTrimmedString(), amountReceivedString: selectedQuote.value.quote.value.toBTCTrimmedString(), - fee: Number(flyoverTotalFee.value), + fee: Number(flyoverTotalFee.value.toBTCTrimmedString()), total: selectedQuote.value.quote.value.plus(flyoverTotalFee.value).toBTCTrimmedString(), recipientAddress: recipientAddress.value, senderAddress: pegInTxState.value.normalizedTx.inputs[0].address, diff --git a/src/pegin/components/create/ConfirmationSteps.vue b/src/pegin/components/create/ConfirmationSteps.vue index 6189b0886..f7bd837f8 100644 --- a/src/pegin/components/create/ConfirmationSteps.vue +++ b/src/pegin/components/create/ConfirmationSteps.vue @@ -415,7 +415,7 @@ import { computed, defineComponent } from 'vue'; import SatoshiBig from '@/common/types/SatoshiBig'; import EnvironmentContextProviderService from '@/common/providers/EnvironmentContextProvider'; import { PegInTxState } from '@/common/types/pegInTx'; -import { QuotePegIn2WP } from '@/common/types'; +import { PeginQuote } from '@/common/types'; import * as constants from '@/common/store/constants'; import { truncateStringToSize, copyToClipboard, getBtcAddressExplorerUrl } from '@/common/utils'; import { useGetter, useState } from '@/common/store/helper'; @@ -434,18 +434,12 @@ export default defineComponent({ const pegInTxState = useState('pegInTx'); const safeFee = useGetter('pegInTx', constants.PEGIN_TX_GET_SAFE_TX_FEE); const walletName = useGetter('pegInTx', constants.WALLET_NAME); - const selectedQuote = useGetter('flyoverPegin', constants.FLYOVER_PEGIN_GET_SELECTED_QUOTE); + const selectedQuote = useGetter('flyoverPegin', constants.FLYOVER_PEGIN_GET_SELECTED_QUOTE); const flyover = computed(() => pegInTxState.value.peginType === constants.peginType.FLYOVER); const changeIdx = flyover.value ? 1 : 2; - function getProviderFee(): SatoshiBig { - return selectedQuote.value.quote.productFeeAmount - .plus(selectedQuote.value.quote.callFee) - .plus(SatoshiBig.fromWeiBig(selectedQuote.value.quote.gasFee)); - } - const amountToTransfer = computed(() => (flyover.value - ? selectedQuote.value.quote.value.plus(getProviderFee()) + ? selectedQuote.value.quote.value.plus(selectedQuote.value.providerFee) : pegInTxState.value.amountToTransfer)); const rskFederationAddress = computed(():string => pegInTxState.value diff --git a/src/pegin/components/create/PegInForm.vue b/src/pegin/components/create/PegInForm.vue index 10cb3828c..047006ba1 100644 --- a/src/pegin/components/create/PegInForm.vue +++ b/src/pegin/components/create/PegInForm.vue @@ -92,7 +92,7 @@ import EnvironmentContextProviderService from '@/common/providers/EnvironmentCon import { TxStatusType } from '@/common/types/store'; import { TxSummaryOrientation } from '@/common/types/Status'; import { - Feature, FeatureNames, FlyoverPeginState, QuotePegIn2WP, SatoshiBig, + Feature, FeatureNames, FlyoverPeginState, PeginQuote, QuotePegIn2WP, SatoshiBig, } from '@/common/types'; import { useAction, useGetter, useState, useStateAttribute, @@ -123,7 +123,7 @@ export default defineComponent({ const showOptions = ref(false); const loadingQuotes = ref(false); const selected = ref(); - const selectedQuote = ref(); + const selectedQuote = ref(); const showErrorDialog = ref(false); const txError = ref(new ServiceError('', '', '', '')); @@ -144,16 +144,11 @@ export default defineComponent({ const selectedAccountBalance = useGetter('pegInTx', constants.PEGIN_TX_GET_SELECTED_BALANCE); const enoughAmountFlyover = computed(() => { - const quote = selectedQuote.value?.quote; - if (!quote) { + if (!selectedQuote.value) { return false; } - const fullAmount: SatoshiBig = quote?.value - .plus(quote.productFeeAmount) - .plus(quote.callFee) - .plus(SatoshiBig.fromWeiBig(quote.gasFee)) + const fullAmount: SatoshiBig = selectedQuote.value.totalValueToTransfer .plus(selectedFee.value); - return selectedAccountBalance.value?.gte(fullAmount); }); @@ -220,10 +215,7 @@ export default defineComponent({ flyoverService.value.acceptPeginQuote(selectedQuoteHash.value) .then((acceptedQuote) => { context.emit('createTx', { - amountToTransferInSatoshi: selectedQuote.value?.quote.value - .plus(selectedQuote.value?.quote.productFeeAmount) - .plus(selectedQuote.value?.quote.callFee) - .plus(SatoshiBig.fromWeiBig(selectedQuote.value?.quote.gasFee)), + amountToTransferInSatoshi: selectedQuote.value?.totalValueToTransfer, refundAddress: '', recipient: '', feeLevel: pegInTxState.value.selectedFee, @@ -236,7 +228,7 @@ export default defineComponent({ pegInFormState.value.send('fill'); } - async function changeSelectedOption(selectedType: constants.peginType, quote?: QuotePegIn2WP) { + async function changeSelectedOption(selectedType: constants.peginType, quote?: PeginQuote) { selected.value = selectedType; await setPeginType(selected.value); selectedQuote.value = quote; diff --git a/src/pegin/components/create/PeginOptionCard.vue b/src/pegin/components/create/PeginOptionCard.vue index 94af8e841..7cfcd7c30 100644 --- a/src/pegin/components/create/PeginOptionCard.vue +++ b/src/pegin/components/create/PeginOptionCard.vue @@ -70,7 +70,7 @@ import { useGetter, useState, useStateAttribute } from '@/common/store/helper'; import { blockConfirmationsToTimeString, truncateString } from '@/common/utils'; import RskAddressInput from '@/pegin/components/create/RskAddressInput.vue'; import EnvironmentContextProviderService from '@/common/providers/EnvironmentContextProvider'; -import { PegInTxState, QuotePegIn2WP, SatoshiBig } from '@/common/types'; +import { PeginQuote, PegInTxState, SatoshiBig } from '@/common/types'; export default defineComponent({ name: 'PeginOptionCard', @@ -87,7 +87,7 @@ export default defineComponent({ default: false, }, quote: { - type: Object as PropType, + type: Object as PropType, }, }, setup(props, context) { @@ -99,13 +99,11 @@ export default defineComponent({ const bitcoinPrice = useStateAttribute('pegInTx', 'bitcoinPrice'); const fixedUSDDecimals = 2; - const quote = computed(() => props.quote?.quote); + const computedQuote = computed(() => props.quote); const quoteFee = computed(() => { - if (!quote.value) return new SatoshiBig('0', 'btc'); - return quote.value.productFeeAmount - .plus(quote.value.callFee) - .plus(SatoshiBig.fromWeiBig(quote.value.gasFee)); + if (!computedQuote.value) return new SatoshiBig('0', 'btc'); + return computedQuote.value.providerFee; }); const PeginOptions = { @@ -125,11 +123,11 @@ export default defineComponent({ label: 'Powered by PowPeg + Flyover', subtitleColor: 'orange', link: 'https://dev.rootstock.io/concepts/rif-suite/#meet-the-suite', - estimatedTime: () => blockConfirmationsToTimeString(quote.value?.confirmations ?? 0, 'btc'), - amountToTransfer: () => quote.value?.value - .plus(quoteFee.value).plus(selectedFee.value) ?? new SatoshiBig('0', 'btc'), + estimatedTime: () => blockConfirmationsToTimeString(computedQuote.value?.quote.confirmations ?? 0, 'btc'), + amountToTransfer: () => computedQuote.value?.totalValueToTransfer + .plus(selectedFee.value) ?? new SatoshiBig('0', 'btc'), providerFee: () => quoteFee.value, - valueToReceive: () => quote.value?.value ?? new SatoshiBig('0', 'btc'), + valueToReceive: () => computedQuote.value?.quote.value ?? new SatoshiBig('0', 'btc'), }, }; const option = computed(() => PeginOptions[props.optionType as keyof typeof PeginOptions]); diff --git a/src/pegin/store/FlyoverPegin/mutations.ts b/src/pegin/store/FlyoverPegin/mutations.ts index d8451e8ef..3f8a635f7 100644 --- a/src/pegin/store/FlyoverPegin/mutations.ts +++ b/src/pegin/store/FlyoverPegin/mutations.ts @@ -1,5 +1,5 @@ import { - FlyoverPeginState, LiquidityProvider2WP, QuotePegIn2WP, SatoshiBig, + FlyoverPeginState, LiquidityProvider2WP, PeginQuote, SatoshiBig, } from '@/common/types'; import { MutationTree } from 'vuex'; import * as constants from '@/common/store/constants'; @@ -14,7 +14,7 @@ export const mutations: MutationTree = { [constants.FLYOVER_PEGIN_SET_PROVIDERS]: (state, providers: Array) => { state.liquidityProviders = providers; }, - [constants.FLYOVER_PEGIN_SET_QUOTES]: (state, quotes: Record>) => { + [constants.FLYOVER_PEGIN_SET_QUOTES]: (state, quotes: Record>) => { state.quotes = quotes; }, [constants.FLYOVER_PEGIN_SET_SELECTED_QUOTE]: (state, quoteHash: string) => { diff --git a/tests/unit/common/PeginQuote.spec.ts b/tests/unit/common/PeginQuote.spec.ts new file mode 100644 index 000000000..c8a88f2e5 --- /dev/null +++ b/tests/unit/common/PeginQuote.spec.ts @@ -0,0 +1,66 @@ +import { PeginQuote, SatoshiBig, WeiBig } from '@/common/types'; +import { Quote } from '@rsksmart/flyover-sdk'; + +describe('PeginQuote', () => { + let quote: Quote; + let peginQuote: PeginQuote; + + const quoteValues = { + fedBTCAddr: '2MxdCCrmUaEG1Tk8dshdcTGKiA9LewNDVCb', + lbcAddr: '0xc2A630c053D12D63d32b025082f6Ba268db18300', + lpRSKAddr: '0x7c4890a0f1d4bbf2c669ac2d1effa185c505359b', + btcRefundAddr: 'mhy7p4F5hn3i1rQCQY3GHXZfMDiUPnJ98S', + rskRefundAddr: '0xB69d88d37e8788F1e8F86FD26c710Eaa93dE3311', + lpBTCAddr: 'mvL2bVzGUeC9oqVyQWJ4PxQspFzKgjzAqe', + callFee: 99996600000000n, + penaltyFee: 10000000000000n, + contractAddr: '0xB69d88d37e8788F1e8F86FD26c710Eaa93dE3311', + data: '', + gasLimit: 21000, + nonce: 8494918057086753218n, + value: 5000000000000000n, + agreementTimestamp: 1728621792, + timeForDeposit: 7200, + lpCallTime: 14400, + confirmations: 2, + callOnRegister: false, + gasFee: 1368444000000n, + productFeeAmount: 0n, + }; + + beforeEach(() => { + quote = { + quote: quoteValues, + quoteHash: '7a8e40ea9266659b4924946ec5ac681887c16f00996326ad8d65222414e679e8', + }; + + peginQuote = new PeginQuote(quote); + }); + + it('should initialize correctly', () => { + expect(peginQuote.quoteHash).toEqual('7a8e40ea9266659b4924946ec5ac681887c16f00996326ad8d65222414e679e8'); + expect(peginQuote.quote.callFee.toBTCTrimmedString()) + .toEqual(SatoshiBig.fromWeiBig(new WeiBig(quoteValues.callFee, 'wei')).toBTCTrimmedString()); + expect(peginQuote.quote.gasFee.toRBTCString()) + .toEqual(new WeiBig(quoteValues.gasFee, 'wei').toRBTCString()); + expect(peginQuote.quote.penaltyFee.toRBTCString()) + .toEqual(new WeiBig(quoteValues.penaltyFee, 'wei').toRBTCString()); + expect(peginQuote.quote.productFeeAmount.toBTCTrimmedString()) + .toEqual(SatoshiBig.fromWeiBig(new WeiBig(quoteValues.productFeeAmount, 'wei')).toBTCTrimmedString()); + expect(peginQuote.quote.value.toBTCTrimmedString()) + .toEqual(SatoshiBig.fromWeiBig(new WeiBig(quoteValues.value, 'wei')).toBTCTrimmedString()); + }); + + it('should calculate providerFee correctly', () => { + const expectedProviderFee = SatoshiBig.fromWeiBig(new WeiBig(quoteValues.callFee, 'wei')) + .plus(SatoshiBig.fromWeiBig(new WeiBig(quoteValues.productFeeAmount, 'wei'))) + .plus(SatoshiBig.fromWeiBig(new WeiBig(quoteValues.gasFee, 'wei'))); + expect(peginQuote.providerFee.toBTCString()).toEqual(expectedProviderFee.toBTCString()); + }); + + it('should calculate totalValueToTransfer correctly', () => { + const expectedTotalValueToTransfer = SatoshiBig.fromWeiBig(new WeiBig(quoteValues.value, 'wei')) + .plus(peginQuote.providerFee); + expect(peginQuote.totalValueToTransfer.toString()).toEqual(expectedTotalValueToTransfer.toString()); + }); +}); From 4a3ec41db74dd35c2c950da2efa63d9596685dcc Mon Sep 17 00:00:00 2001 From: ronaldsg Date: Wed, 16 Oct 2024 10:38:23 -0500 Subject: [PATCH 022/104] Add network fee on the quote calculation I've added 2 methods in order to calculate the full tx amounts using the external Network fee value --- src/common/types/Flyover/PeginQuote.ts | 12 +++++++++++- src/pegin/components/create/ConfirmTx.vue | 10 +++++----- src/pegin/components/create/ConfirmationSteps.vue | 2 +- src/pegin/components/create/PegInForm.vue | 5 ++--- src/pegin/components/create/PeginOptionCard.vue | 4 ++-- tests/unit/common/PeginQuote.spec.ts | 15 ++++++++++++++- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/common/types/Flyover/PeginQuote.ts b/src/common/types/Flyover/PeginQuote.ts index ca93c5eab..5ef819c7c 100644 --- a/src/common/types/Flyover/PeginQuote.ts +++ b/src/common/types/Flyover/PeginQuote.ts @@ -21,7 +21,7 @@ export default class PeginQuote implements QuotePegIn2WP { this.quoteHash = quoteHash; } - get totalValueToTransfer(): SatoshiBig { + get valueToTransfer(): SatoshiBig { return this.quote.value .plus(this.providerFee); } @@ -31,4 +31,14 @@ export default class PeginQuote implements QuotePegIn2WP { .plus(this.quote.callFee) .plus(SatoshiBig.fromWeiBig(this.quote.gasFee)); } + + getTotalQuoteFee(btcNetworkFee: SatoshiBig): SatoshiBig { + return this.providerFee + .plus(btcNetworkFee); + } + + getTotalTxAmount(btcNetworkFee: SatoshiBig): SatoshiBig { + return this.valueToTransfer + .plus(btcNetworkFee); + } } diff --git a/src/pegin/components/create/ConfirmTx.vue b/src/pegin/components/create/ConfirmTx.vue index cb6bbd622..a4f68b1db 100644 --- a/src/pegin/components/create/ConfirmTx.vue +++ b/src/pegin/components/create/ConfirmTx.vue @@ -141,14 +141,14 @@ export default defineComponent({ senderAddress: pegInTxState.value.normalizedTx.inputs[0].address, })); - const flyoverTotalFee = computed(() => selectedQuote.value.providerFee - .plus(safeFee.value)); + // const flyoverTotalFee = computed(() => selectedQuote.value.providerFee + // .plus(safeFee.value)); const flyoverPeginSummary = computed((): NormalizedSummary => ({ amountFromString: selectedQuote.value.quote.value.toBTCTrimmedString(), amountReceivedString: selectedQuote.value.quote.value.toBTCTrimmedString(), - fee: Number(flyoverTotalFee.value.toBTCTrimmedString()), - total: selectedQuote.value.quote.value.plus(flyoverTotalFee.value).toBTCTrimmedString(), + fee: Number(selectedQuote.value.getTotalQuoteFee(safeFee.value).toBTCTrimmedString()), + total: selectedQuote.value.getTotalTxAmount(safeFee.value).toBTCTrimmedString(), recipientAddress: recipientAddress.value, senderAddress: pegInTxState.value.normalizedTx.inputs[0].address, })); @@ -159,7 +159,7 @@ export default defineComponent({ const flyoverProps = computed(() => ({ value: Number(selectedQuote.value.quote.value.toBTCTrimmedString()), - fee: Number(flyoverTotalFee.value.toBTCTrimmedString()), + fee: Number(selectedQuote.value.getTotalQuoteFee(safeFee.value).toBTCTrimmedString()), provider: getLPName(), details: { senderAddress: pegInTxState.value.normalizedTx.inputs[0].address, diff --git a/src/pegin/components/create/ConfirmationSteps.vue b/src/pegin/components/create/ConfirmationSteps.vue index f7bd837f8..76294b803 100644 --- a/src/pegin/components/create/ConfirmationSteps.vue +++ b/src/pegin/components/create/ConfirmationSteps.vue @@ -439,7 +439,7 @@ export default defineComponent({ const changeIdx = flyover.value ? 1 : 2; const amountToTransfer = computed(() => (flyover.value - ? selectedQuote.value.quote.value.plus(selectedQuote.value.providerFee) + ? selectedQuote.value.valueToTransfer : pegInTxState.value.amountToTransfer)); const rskFederationAddress = computed(():string => pegInTxState.value diff --git a/src/pegin/components/create/PegInForm.vue b/src/pegin/components/create/PegInForm.vue index 047006ba1..d48f534ef 100644 --- a/src/pegin/components/create/PegInForm.vue +++ b/src/pegin/components/create/PegInForm.vue @@ -147,8 +147,7 @@ export default defineComponent({ if (!selectedQuote.value) { return false; } - const fullAmount: SatoshiBig = selectedQuote.value.totalValueToTransfer - .plus(selectedFee.value); + const fullAmount: SatoshiBig = selectedQuote.value.getTotalTxAmount(selectedFee.value); return selectedAccountBalance.value?.gte(fullAmount); }); @@ -215,7 +214,7 @@ export default defineComponent({ flyoverService.value.acceptPeginQuote(selectedQuoteHash.value) .then((acceptedQuote) => { context.emit('createTx', { - amountToTransferInSatoshi: selectedQuote.value?.totalValueToTransfer, + amountToTransferInSatoshi: selectedQuote.value?.valueToTransfer, refundAddress: '', recipient: '', feeLevel: pegInTxState.value.selectedFee, diff --git a/src/pegin/components/create/PeginOptionCard.vue b/src/pegin/components/create/PeginOptionCard.vue index 7cfcd7c30..ea5580a14 100644 --- a/src/pegin/components/create/PeginOptionCard.vue +++ b/src/pegin/components/create/PeginOptionCard.vue @@ -124,8 +124,8 @@ export default defineComponent({ subtitleColor: 'orange', link: 'https://dev.rootstock.io/concepts/rif-suite/#meet-the-suite', estimatedTime: () => blockConfirmationsToTimeString(computedQuote.value?.quote.confirmations ?? 0, 'btc'), - amountToTransfer: () => computedQuote.value?.totalValueToTransfer - .plus(selectedFee.value) ?? new SatoshiBig('0', 'btc'), + amountToTransfer: () => computedQuote.value?.getTotalTxAmount(selectedFee.value) + ?? new SatoshiBig('0', 'btc'), providerFee: () => quoteFee.value, valueToReceive: () => computedQuote.value?.quote.value ?? new SatoshiBig('0', 'btc'), }, diff --git a/tests/unit/common/PeginQuote.spec.ts b/tests/unit/common/PeginQuote.spec.ts index c8a88f2e5..105276a22 100644 --- a/tests/unit/common/PeginQuote.spec.ts +++ b/tests/unit/common/PeginQuote.spec.ts @@ -61,6 +61,19 @@ describe('PeginQuote', () => { it('should calculate totalValueToTransfer correctly', () => { const expectedTotalValueToTransfer = SatoshiBig.fromWeiBig(new WeiBig(quoteValues.value, 'wei')) .plus(peginQuote.providerFee); - expect(peginQuote.totalValueToTransfer.toString()).toEqual(expectedTotalValueToTransfer.toString()); + expect(peginQuote.valueToTransfer.toString()).toEqual(expectedTotalValueToTransfer.toString()); }); + + it('should calculate totalQuoteFee correctly', () => { + const btcNetworkFee = new SatoshiBig(100000000, 'satoshi'); + const expectedTotalQuoteFee = peginQuote.providerFee.plus(btcNetworkFee); + expect(peginQuote.getTotalQuoteFee(btcNetworkFee).toString()).toEqual(expectedTotalQuoteFee.toString()); + }); + + it('should calculate totalTxAmount correctly', () => { + const btcNetworkFee = new SatoshiBig(100000000, 'satoshi'); + const expectedTotalTxAmount = peginQuote.valueToTransfer.plus(btcNetworkFee); + expect(peginQuote.getTotalTxAmount(btcNetworkFee).toString()).toEqual(expectedTotalTxAmount.toString()); + }); + }); From 6f74b996ca250d2d12140f7a3488d66b56a698ee Mon Sep 17 00:00:00 2001 From: lserra-iov Date: Thu, 10 Oct 2024 15:21:37 -0300 Subject: [PATCH 023/104] Show pegin wallets based on feature flags --- .../components/exchange/SelectBitcoinWallet.vue | 12 +++++++++--- src/common/types/Feature.ts | 8 +++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/common/components/exchange/SelectBitcoinWallet.vue b/src/common/components/exchange/SelectBitcoinWallet.vue index ba9a09bc6..4377679bf 100644 --- a/src/common/components/exchange/SelectBitcoinWallet.vue +++ b/src/common/components/exchange/SelectBitcoinWallet.vue @@ -42,15 +42,16 @@ diff --git a/src/pegout/components/RbtcInputAmount.vue b/src/pegout/components/RbtcInputAmount.vue deleted file mode 100644 index 8974d31c7..000000000 --- a/src/pegout/components/RbtcInputAmount.vue +++ /dev/null @@ -1,291 +0,0 @@ - - - diff --git a/src/pegout/components/RskWalletConnection.vue b/src/pegout/components/RskWalletConnection.vue deleted file mode 100644 index bc1201acc..000000000 --- a/src/pegout/components/RskWalletConnection.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - diff --git a/src/pegout/views/PegOut.vue b/src/pegout/views/PegOut.vue index 619a87980..268cc1577 100644 --- a/src/pegout/views/PegOut.vue +++ b/src/pegout/views/PegOut.vue @@ -10,7 +10,6 @@ diff --git a/src/common/router/index.ts b/src/common/router/index.ts index dee462ce4..4f262685d 100644 --- a/src/common/router/index.ts +++ b/src/common/router/index.ts @@ -13,13 +13,7 @@ async function checkAcceptedTerms( next: NavigationGuardNext, ) { const store = useStore(); - if (store.state.web3Session.acceptedTerms === undefined) { - await store.dispatch(`web3Session/${constants.SESSION_ADD_TERMS_AND_CONDITIONS_ENABLED}`); - } - if ( - !store.state.web3Session.termsAndConditionsEnabled - || (store.state.web3Session.termsAndConditionsEnabled && store.state.web3Session.acceptedTerms) - ) { + if (store.state.web3Session.acceptedTerms) { next(); } else { next({ name: 'Home' }); @@ -92,13 +86,6 @@ const routes: Readonly = [ component: () => import(/* webpackChunkName: "pegin-create" */ '../../pegin/views/Create.vue'), beforeEnter: checkAcceptedTerms, }, - { - path: '/pegin/:wallet/success/:txId', - name: 'Success', - component: - () => import(/* webpackChunkName: "pegin-success" */ '../../pegin/views/Success.vue'), - beforeEnter: [checkFromRoute], - }, { path: '/:type/success/tx/:txId', name: 'SuccessTx', @@ -118,12 +105,4 @@ const router = createRouter({ routes, }); -router.beforeResolve((to, from, next) => { - const store = useStore(); - store.dispatch(`view/${constants.VIEW_ADD_CURRENT_VIEW}`, to.name); - const inTxFlow = store.getters[`web3Session/${constants.SESSION_IN_TX_FLOW}`]; - if (to.name === 'Create' && !inTxFlow) next({ name: 'Home' }); - else next(); -}); - export default router; diff --git a/src/common/store/constants.ts b/src/common/store/constants.ts index 8ff6f6a9d..2bc26b5e1 100644 --- a/src/common/store/constants.ts +++ b/src/common/store/constants.ts @@ -80,9 +80,6 @@ export const FLYOVER_PEGOUT_CLEAR_QUOTES = 'FLYOVER_PEGOUT_CLEAR_QUOTES'; export const FLYOVER_PEGOUT_SET_SELECTED_QUOTE_HASH = 'FLYOVER_PEGOUT_SET_SELECTED_QUOTE_HASH'; export const FLYOVER_PEGOUT_CLEAR_QUOTE_DIFFERENCES = 'FLYOVER_PEGOUT_CLEAR_QUOTE_DIFFERENCES'; -// View actions -export const VIEW_ADD_CURRENT_VIEW = 'VIEW_ADD_CURRENT_VIEW'; - // Flyover PegIn actions export const FLYOVER_PEGIN_INIT = 'FLYOVER_PEGIN_INIT'; @@ -164,9 +161,6 @@ export const FLYOVER_PEGOUT_SET_TX_HASH = 'FLYOVER_PEGOUT_SET_TX_HASH'; export const FLYOVER_PEGOUT_SET_SELECTED_QUOTE = 'FLYOVER_PEGOUT_SET_SELECTED_QUOTE'; export const FLYOVER_PEGOUT_SET_QUOTES_DIFFERENCES = 'FLYOVER_PEGOUT_SET_QUOTES_DIFFERENCES'; -// View mutations -export const VIEW_SET_CURRENT_VIEW = 'VIEW_SET_CURRENT_VIEW'; - // Flyover PegIn mutations export const FLYOVER_PEGIN_SET_PROVIDERS = 'FLYOVER_PEGIN_SET_PROVIDERS'; @@ -210,6 +204,8 @@ export const PEGIN_TX_IS_ENOUGH_BALANCE = 'PEGIN_TX_IS_ENOUGH_BALANCE'; export const PEGIN_TX_GET_SELECTED_ACCOUNT_TYPE = 'PEGIN_TX_GET_SELECTED_ACCOUNT_TYPE'; export const PEGIN_TX_GET_ACCOUNT_UTXO_LIST = 'PEGIN_TX_GET_ACCOUNT_UTXO_LIST'; export const PEGIN_TX_GET_SELECTED_UTXO_LIST = 'PEGIN_TX_GET_SELECTED_UTXO_LIST'; +export const PEGIN_TX_IS_HD_WALLET = 'PEGIN_TX_IS_HD_WALLET'; +export const PEGIN_TX_IS_SF_WALLET = 'PEGIN_TX_IS_SF_WALLET'; // PegOut tx getters export const PEGOUT_TX_GET_SAFE_TX_FEE = 'PEGOUT_TX_GET_SAFE_TX_FEE'; @@ -220,9 +216,6 @@ export const PEGOUT_TX_EVENT_TRANSACTION_HASH = 'transactionHash'; export const PEGIN_TX_GET_ENOUGH_FEE_VALUE = 'PEGIN_TX_GET_ENOUGH_FEE_VALUE'; // View getters -export const VIEW_GET_CURRENT_VIEW = 'VIEW_GET_CURRENT_VIEW'; -export const PEGIN_TX_IS_HD_WALLET = 'PEGIN_TX_IS_HD_WALLET'; -export const PEGIN_TX_IS_SF_WALLET = 'PEGIN_TX_IS_SF_WALLET'; // Session getters export const SESSION_IN_TX_FLOW = 'SESSION_IN_TX_FLOW'; diff --git a/src/common/store/index.ts b/src/common/store/index.ts index 415ae3592..0a2f6501b 100644 --- a/src/common/store/index.ts +++ b/src/common/store/index.ts @@ -6,7 +6,6 @@ import { flyoverPegin } from '@/pegin/store/FlyoverPegin'; import { pegOutTx } from '@/pegout/store/pegoutTx'; import { flyoverPegout } from '@/pegout/store/FlyoverPegout'; import { web3Session } from './session'; -import { view } from './view'; import pkg from '../../../package.json'; const store: StoreOptions = { @@ -19,7 +18,6 @@ const store: StoreOptions = { modules: { pegInTx, web3Session, - view, status, pegOutTx, flyoverPegout, diff --git a/src/common/store/view/actions.ts b/src/common/store/view/actions.ts deleted file mode 100644 index 2d771f155..000000000 --- a/src/common/store/view/actions.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ActionTree } from 'vuex'; -import { ViewState, RootState } from '@/common/types'; -import * as constants from '@/common/store/constants'; - -export const actions: ActionTree = { - [constants.VIEW_ADD_CURRENT_VIEW]: ({ commit }, view: string): void => { - commit(constants.VIEW_SET_CURRENT_VIEW, view); - }, -}; diff --git a/src/common/store/view/getters.ts b/src/common/store/view/getters.ts deleted file mode 100644 index bd7e4920f..000000000 --- a/src/common/store/view/getters.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { GetterTree } from 'vuex'; -import * as constants from '@/common/store/constants'; -import { ViewState, RootState } from '@/common/types'; - -export const getters: GetterTree = { - [constants.VIEW_GET_CURRENT_VIEW]: (state) => state.currentView, -}; diff --git a/src/common/store/view/index.ts b/src/common/store/view/index.ts deleted file mode 100644 index aa66c649d..000000000 --- a/src/common/store/view/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Module } from 'vuex'; -import { ViewState, RootState } from '@/common/types'; -import { actions } from './actions'; -import { mutations } from './mutations'; -import { getters } from './getters'; - -export const state: ViewState = { - currentView: '', -}; - -const namespaced = true; - -export const view: Module = { - namespaced, - state, - mutations, - actions, - getters, -}; diff --git a/src/common/store/view/mutations.ts b/src/common/store/view/mutations.ts deleted file mode 100644 index 0321180e9..000000000 --- a/src/common/store/view/mutations.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { MutationTree } from 'vuex'; -import * as constants from '@/common/store/constants'; -import { ViewState } from '@/common/types'; - -export const mutations: MutationTree = { - [constants.VIEW_SET_CURRENT_VIEW]: (state, view: string) => { - state.currentView = view; - }, -}; diff --git a/src/pegin/components/create/ConfirmTx.vue b/src/pegin/components/create/ConfirmTx.vue index a4f68b1db..199cc5890 100644 --- a/src/pegin/components/create/ConfirmTx.vue +++ b/src/pegin/components/create/ConfirmTx.vue @@ -64,6 +64,7 @@ export default defineComponent({ StatusSummary, ConfirmationSteps, }, + emits: ['successConfirmation', 'toPegInForm'], props: { confirmTxState: { type: Object as PropType>, diff --git a/src/pegin/components/create/PegInForm.vue b/src/pegin/components/create/PegInForm.vue index d48f534ef..716e933eb 100644 --- a/src/pegin/components/create/PegInForm.vue +++ b/src/pegin/components/create/PegInForm.vue @@ -113,6 +113,7 @@ export default defineComponent({ BtcFeeSelect, FullTxErrorDialog, }, + emits: ['back', 'createTx'], setup(_, context) { const pegInFormState = ref>(new Machine('fill')); const showWarningMessage = ref(false); diff --git a/src/pegin/store/PeginTx/getters.ts b/src/pegin/store/PeginTx/getters.ts index c60a4b9b1..0121cadba 100644 --- a/src/pegin/store/PeginTx/getters.ts +++ b/src/pegin/store/PeginTx/getters.ts @@ -61,13 +61,10 @@ export const getters: GetterTree = { return address; }, [constants.PEGIN_TX_GET_REFUND_ADDRESS]: - (state: PegInTxState, localGetters, rootState?:RootState, rootGetters?) => { + (state: PegInTxState, localGetters) => { let address = ''; - const currentView = rootGetters[constants.VIEW_GET_CURRENT_VIEW]; const coin = EnvironmentAccessorService.getEnvironmentVariables().vueAppCoin; - if (currentView && currentView === 'Status') { - address = state.statusInfo.refundAddress; - } else if (localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.LEATHER.formal_name + if (localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.LEATHER.formal_name || localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.XVERSE.formal_name || localGetters[constants.WALLET_NAME] === constants.WALLET_NAMES.ENKRYPT.formal_name ) { @@ -115,25 +112,20 @@ export const getters: GetterTree = { return publicKey; }, [constants.PEGIN_TX_GET_SAFE_TX_FEE]: - (state: PegInTxState, localGetters?, rootState?: RootState, rootGetters?) + (state: PegInTxState) : SatoshiBig => { let fee: SatoshiBig; if (!state.normalizedTx.inputs.length) { - const currentView = rootGetters[`view/${constants.VIEW_GET_CURRENT_VIEW}`]; - if (currentView && currentView === 'Status') { - fee = state.statusInfo.safeFee; - } else { - switch (state.selectedFee) { - case constants.BITCOIN_SLOW_FEE_LEVEL: - fee = state.calculatedFees.slow.amount; - break; - case constants.BITCOIN_FAST_FEE_LEVEL: - fee = state.calculatedFees.fast.amount; - break; - default: - fee = state.calculatedFees.average.amount; - break; - } + switch (state.selectedFee) { + case constants.BITCOIN_SLOW_FEE_LEVEL: + fee = state.calculatedFees.slow.amount; + break; + case constants.BITCOIN_FAST_FEE_LEVEL: + fee = state.calculatedFees.fast.amount; + break; + default: + fee = state.calculatedFees.average.amount; + break; } } else { const inputsAmonut = state.normalizedTx.inputs diff --git a/src/pegin/views/Success.vue b/src/pegin/views/Success.vue deleted file mode 100644 index a93ff7fd1..000000000 --- a/src/pegin/views/Success.vue +++ /dev/null @@ -1,83 +0,0 @@ - - - diff --git a/src/pegout/components/FlyoverPegout.vue b/src/pegout/components/PegoutForm.vue similarity index 98% rename from src/pegout/components/FlyoverPegout.vue rename to src/pegout/components/PegoutForm.vue index bf5885a8a..a529a962f 100644 --- a/src/pegout/components/FlyoverPegout.vue +++ b/src/pegout/components/PegoutForm.vue @@ -10,7 +10,7 @@ - @@ -101,7 +101,7 @@ import { } from 'vue'; import * as constants from '@/common/store/constants'; import EnvironmentContextProviderService from '@/common/providers/EnvironmentContextProvider'; -import FlyoverRbtcInputAmount from '@/pegout/components/FlyoverRbtcInputAmount.vue'; +import RbtcInputAmount from '@/pegout/components/RbtcInputAmount.vue'; import AddressDialog from '@/pegout/components/AddressDialog.vue'; import QuoteDiffDialog from '@/pegout/components/QuoteDiffDialog.vue'; import { @@ -122,9 +122,9 @@ import FullTxErrorDialog from '@/common/components/exchange/FullTxErrorDialog.vu import PegoutOption from './PegoutOption.vue'; export default defineComponent({ - name: 'FlyoverPegout', + name: 'PegoutForm', components: { - FlyoverRbtcInputAmount, + RbtcInputAmount, AddressDialog, PegoutOption, FullTxErrorDialog, diff --git a/src/pegout/components/FlyoverRbtcInputAmount.vue b/src/pegout/components/RbtcInputAmount.vue similarity index 99% rename from src/pegout/components/FlyoverRbtcInputAmount.vue rename to src/pegout/components/RbtcInputAmount.vue index 18a35a1b4..204cbbc0a 100644 --- a/src/pegout/components/FlyoverRbtcInputAmount.vue +++ b/src/pegout/components/RbtcInputAmount.vue @@ -91,7 +91,7 @@ import { } from '@/common/store/helper'; export default defineComponent({ - name: 'FlyoverRbtcInputAmount', + name: 'RbtcInputAmount', emits: ['get-quotes'], props: { willReceive: { diff --git a/src/pegout/views/PegOut.vue b/src/pegout/views/PegOut.vue index 268cc1577..17d5e692e 100644 --- a/src/pegout/views/PegOut.vue +++ b/src/pegout/views/PegOut.vue @@ -1,44 +1,28 @@ diff --git a/src/pegin/components/create/PegInForm.vue b/src/pegin/components/create/PegInForm.vue index b5969295b..af84f8814 100644 --- a/src/pegin/components/create/PegInForm.vue +++ b/src/pegin/components/create/PegInForm.vue @@ -1,33 +1,47 @@ diff --git a/src/pegin/components/create/SendBitcoin.vue b/src/pegin/components/create/SendBitcoin.vue index b1fc6e07e..14f948f46 100644 --- a/src/pegin/components/create/SendBitcoin.vue +++ b/src/pegin/components/create/SendBitcoin.vue @@ -1,5 +1,5 @@