diff --git a/package-lock.json b/package-lock.json index 2fdf7fb3f..672ce246c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.2.1", "dependencies": { "@react-spring/web": "^9.6.1", - "@secretkeylabs/xverse-core": "0.7.3", + "@secretkeylabs/xverse-core": "0.8.0", "@stacks/connect": "^6.10.2", "@stacks/encryption": "4.3.5", "@stacks/stacks-blockchain-api-types": "^6.1.1", @@ -110,6 +110,43 @@ "webpack-dev-server": "^4.11.0" } }, + "../../../../desktop/work/skl/xverse-core": { + "name": "@secretkeylabs/xverse-core", + "version": "0.8.0", + "extraneous": true, + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@noble/secp256k1": "^1.7.1", + "@scure/base": "^1.1.1", + "@stacks/encryption": "6.1.1", + "@stacks/network": "4.3.5", + "@stacks/storage": "^6.0.0", + "@stacks/transactions": "4.3.5", + "@stacks/wallet-sdk": "^5.0.2", + "axios": "0.27.2", + "bignumber.js": "9.1.0", + "bip32": "^2.0.6", + "bip39": "3.0.3", + "bitcoin-address-validation": "^2.2.1", + "bitcoinjs-lib": "5.2.0", + "bn.js": "^5.1.3", + "buffer": "6.0.3", + "ecpair": "^2.1.0", + "jsontokens": "^4.0.1", + "micro-btc-signer": "^0.4.2", + "process": "^0.11.10", + "util": "^0.12.4" + }, + "devDependencies": { + "rimraf": "^3.0.2", + "ts-loader": "^9.4.1", + "typescript": "^4.8.3", + "vitest": "^0.28.5", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" + } + }, "node_modules/@adobe/css-tools": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.1.0.tgz", @@ -1613,9 +1650,9 @@ "dev": true }, "node_modules/@noble/curves": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-0.7.1.tgz", - "integrity": "sha512-dodwTK3H/0h8BY59J6RxHLC12JPXxmbZUiX5LNX8PjmShcHQXCXaIvXnSXJpFPzAN08SKd8rTrLrzLLNiwU90g==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-0.7.2.tgz", + "integrity": "sha512-tFrNCkH13EJM7qZI1GHXzaWo92bfbkeZbARVax109yEfdDyrhTJU+eFfDXUTFMtgJ9lCfvSyhmsErNo4MPqouQ==", "funding": [ { "type": "individual", @@ -1919,9 +1956,9 @@ } }, "node_modules/@secretkeylabs/xverse-core": { - "version": "0.7.3", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/0.7.3/17b23a971ca01d41255d20bb17c43ed2944f9e50", - "integrity": "sha512-jajL4AKjlCrfiBQbF7V++ZkA7v1GWqVku5/8TE2kMb0FEFFXrSAymHAfBq4BPERTXAI2CA6csoAvDjMXcQvDiA==", + "version": "0.8.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/0.8.0/9faae631820759cbbfa7f010af3498a7f46363df", + "integrity": "sha512-824kwKdHPuuOsiw42rRf7o9W98y7u5dODLBWORKxxzLEF/BbaXC4r6Wk/f5TS+ImPzOTiPvTSzfD47LKorBFGA==", "hasInstallScript": true, "license": "ISC", "dependencies": { @@ -1942,7 +1979,7 @@ "buffer": "6.0.3", "ecpair": "^2.1.0", "jsontokens": "^4.0.1", - "micro-btc-signer": "^0.4.0", + "micro-btc-signer": "^0.4.2", "process": "^0.11.10", "util": "^0.12.4" } @@ -2320,13 +2357,13 @@ "integrity": "sha512-zrjKPGJN4p1azzmh8j0Yj+ZjQ0L9F01qJjAxOtBpapmFbGr1NUuPT1GthIg76y+dobdjSDPN39LpoJG/FbWFLw==" }, "node_modules/@stacks/storage": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.1.1.tgz", - "integrity": "sha512-zqbmoJ95L8DTgpQG5k0MvV8cornkrfTNnVEYDp7nefGiVnH3QR6Mx0xnKlOo4bI4ebHDJ0a2gtWo7Ke9HYEoUw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.2.0.tgz", + "integrity": "sha512-RgNy57pCt+KdQzFdG/3BVFrXQewtzFO+HuneSiAbHbUej6CqaiZbY/eTaOldccQDsxCjQsS4I8WS9L8dg3Hbdg==", "dependencies": { - "@stacks/auth": "^6.1.1", + "@stacks/auth": "^6.2.0", "@stacks/common": "^6.0.0", - "@stacks/encryption": "^6.1.1", + "@stacks/encryption": "^6.2.0", "@stacks/network": "^6.1.1", "base64-js": "^1.5.1", "jsontokens": "^4.0.1" @@ -2359,17 +2396,16 @@ } }, "node_modules/@stacks/storage/node_modules/@stacks/auth": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.1.1.tgz", - "integrity": "sha512-as5ouLTFoNlcKiHzQ4H9dGAlrwj4aX0KxXr5CHV/BPAAzbLtrUNuJZBgAwVZWw65ebCr3JMTz8rMHTRvCO+ITA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.2.0.tgz", + "integrity": "sha512-ZE2OKLV9IRKw37SJ2L4Quuf2NlkUONaMNa0+uuJBONTs8MYdyB6T3EjkmD/fvGxiN65dsCdDzDx7BWtWWonBrQ==", "dependencies": { "@stacks/common": "^6.0.0", - "@stacks/encryption": "^6.1.1", + "@stacks/encryption": "^6.2.0", "@stacks/network": "^6.1.1", - "@stacks/profile": "^6.1.1", + "@stacks/profile": "^6.2.0", "cross-fetch": "^3.1.5", - "jsontokens": "^4.0.1", - "query-string": "^6.13.1" + "jsontokens": "^4.0.1" } }, "node_modules/@stacks/storage/node_modules/@stacks/common": { @@ -2382,9 +2418,9 @@ } }, "node_modules/@stacks/storage/node_modules/@stacks/encryption": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.1.1.tgz", - "integrity": "sha512-tS0Foo83h1ixwxCAbFh3uX0Hty3b3EFcqOb62udgBFlDmsjBD9Pld8t+ApKLTwiad6YhRu6rW+hDAJmhYQToMw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.2.0.tgz", + "integrity": "sha512-l0jeTtnmP2fJJk4rHR8sUjFr67e8Pnz4p7RkiGZm7H+uQ6HKPchgAzySFzgO3EQegRpbUQA6UOPEWJRbdO/kzw==", "dependencies": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -2407,22 +2443,22 @@ } }, "node_modules/@stacks/storage/node_modules/@stacks/profile": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.1.1.tgz", - "integrity": "sha512-r+cDdafKBkUUXtfGSo1MrzK090dqoJgJZaNUmkvoSW1V7b5sX1qkWx1zQJ/C74XOqBex1A6GAaVS62hnMC6E1w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.2.0.tgz", + "integrity": "sha512-3Ib7Y4/gX33S6dywBarLTtCwV0t+1lQGNkpOMhU3tLxA0zgL5NPvfP0x33iZzG9oF0aj13y3z+mT1USVxPEVhQ==", "dependencies": { "@stacks/common": "^6.0.0", "@stacks/network": "^6.1.1", - "@stacks/transactions": "^6.1.1", + "@stacks/transactions": "^6.2.0", "jsontokens": "^4.0.1", "schema-inspector": "^2.0.2", "zone-file": "^2.0.0-beta.3" } }, "node_modules/@stacks/storage/node_modules/@stacks/transactions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.1.1.tgz", - "integrity": "sha512-RyM4Y+UWXpz/seMa9B6MDqpneW+PFdcHIeF36p8X+N1b5yBJardg6LdI7VWn5KG4hKofYttwaVTIjotKYGRIRw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.2.0.tgz", + "integrity": "sha512-FUqcCNhCC5VlPvfUeeC6TB0gUifFj06E9xlWKbi2jB+oObh6XNBu4fS6I4EKxF1tEtUX9RNXcVbKDgMlX38syw==", "dependencies": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -13514,9 +13550,9 @@ } }, "node_modules/sha256-uint8array": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.3.tgz", - "integrity": "sha512-SFTs87RfXVulKrhhP6B5/qcFruOKQZaKf6jY9V4PJ7NOG0qIlQP6XL4pQq5xagsuP/Wd55S7tUBJpRajEsDUEQ==" + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.5.tgz", + "integrity": "sha512-KjYP8M6y8VvV62lnSSZwzutuwqphVOOVQamP+GmmClZcYhbq1HSIw/M2tlKgHndiaIwe3tFG5X38YYsQfUdItw==" }, "node_modules/shallow-clone": { "version": "3.0.1", @@ -16683,9 +16719,9 @@ "dev": true }, "@noble/curves": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-0.7.1.tgz", - "integrity": "sha512-dodwTK3H/0h8BY59J6RxHLC12JPXxmbZUiX5LNX8PjmShcHQXCXaIvXnSXJpFPzAN08SKd8rTrLrzLLNiwU90g==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-0.7.2.tgz", + "integrity": "sha512-tFrNCkH13EJM7qZI1GHXzaWo92bfbkeZbARVax109yEfdDyrhTJU+eFfDXUTFMtgJ9lCfvSyhmsErNo4MPqouQ==", "requires": { "@noble/hashes": "1.2.0" } @@ -16880,9 +16916,9 @@ } }, "@secretkeylabs/xverse-core": { - "version": "0.7.3", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/0.7.3/17b23a971ca01d41255d20bb17c43ed2944f9e50", - "integrity": "sha512-jajL4AKjlCrfiBQbF7V++ZkA7v1GWqVku5/8TE2kMb0FEFFXrSAymHAfBq4BPERTXAI2CA6csoAvDjMXcQvDiA==", + "version": "0.8.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/0.8.0/9faae631820759cbbfa7f010af3498a7f46363df", + "integrity": "sha512-824kwKdHPuuOsiw42rRf7o9W98y7u5dODLBWORKxxzLEF/BbaXC4r6Wk/f5TS+ImPzOTiPvTSzfD47LKorBFGA==", "requires": { "@noble/secp256k1": "^1.7.1", "@scure/base": "^1.1.1", @@ -16901,7 +16937,7 @@ "buffer": "6.0.3", "ecpair": "^2.1.0", "jsontokens": "^4.0.1", - "micro-btc-signer": "^0.4.0", + "micro-btc-signer": "^0.4.2", "process": "^0.11.10", "util": "^0.12.4" }, @@ -17246,13 +17282,13 @@ "integrity": "sha512-zrjKPGJN4p1azzmh8j0Yj+ZjQ0L9F01qJjAxOtBpapmFbGr1NUuPT1GthIg76y+dobdjSDPN39LpoJG/FbWFLw==" }, "@stacks/storage": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.1.1.tgz", - "integrity": "sha512-zqbmoJ95L8DTgpQG5k0MvV8cornkrfTNnVEYDp7nefGiVnH3QR6Mx0xnKlOo4bI4ebHDJ0a2gtWo7Ke9HYEoUw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.2.0.tgz", + "integrity": "sha512-RgNy57pCt+KdQzFdG/3BVFrXQewtzFO+HuneSiAbHbUej6CqaiZbY/eTaOldccQDsxCjQsS4I8WS9L8dg3Hbdg==", "requires": { - "@stacks/auth": "^6.1.1", + "@stacks/auth": "^6.2.0", "@stacks/common": "^6.0.0", - "@stacks/encryption": "^6.1.1", + "@stacks/encryption": "^6.2.0", "@stacks/network": "^6.1.1", "base64-js": "^1.5.1", "jsontokens": "^4.0.1" @@ -17273,17 +17309,16 @@ } }, "@stacks/auth": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.1.1.tgz", - "integrity": "sha512-as5ouLTFoNlcKiHzQ4H9dGAlrwj4aX0KxXr5CHV/BPAAzbLtrUNuJZBgAwVZWw65ebCr3JMTz8rMHTRvCO+ITA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.2.0.tgz", + "integrity": "sha512-ZE2OKLV9IRKw37SJ2L4Quuf2NlkUONaMNa0+uuJBONTs8MYdyB6T3EjkmD/fvGxiN65dsCdDzDx7BWtWWonBrQ==", "requires": { "@stacks/common": "^6.0.0", - "@stacks/encryption": "^6.1.1", + "@stacks/encryption": "^6.2.0", "@stacks/network": "^6.1.1", - "@stacks/profile": "^6.1.1", + "@stacks/profile": "^6.2.0", "cross-fetch": "^3.1.5", - "jsontokens": "^4.0.1", - "query-string": "^6.13.1" + "jsontokens": "^4.0.1" } }, "@stacks/common": { @@ -17296,9 +17331,9 @@ } }, "@stacks/encryption": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.1.1.tgz", - "integrity": "sha512-tS0Foo83h1ixwxCAbFh3uX0Hty3b3EFcqOb62udgBFlDmsjBD9Pld8t+ApKLTwiad6YhRu6rW+hDAJmhYQToMw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.2.0.tgz", + "integrity": "sha512-l0jeTtnmP2fJJk4rHR8sUjFr67e8Pnz4p7RkiGZm7H+uQ6HKPchgAzySFzgO3EQegRpbUQA6UOPEWJRbdO/kzw==", "requires": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -17321,22 +17356,22 @@ } }, "@stacks/profile": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.1.1.tgz", - "integrity": "sha512-r+cDdafKBkUUXtfGSo1MrzK090dqoJgJZaNUmkvoSW1V7b5sX1qkWx1zQJ/C74XOqBex1A6GAaVS62hnMC6E1w==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.2.0.tgz", + "integrity": "sha512-3Ib7Y4/gX33S6dywBarLTtCwV0t+1lQGNkpOMhU3tLxA0zgL5NPvfP0x33iZzG9oF0aj13y3z+mT1USVxPEVhQ==", "requires": { "@stacks/common": "^6.0.0", "@stacks/network": "^6.1.1", - "@stacks/transactions": "^6.1.1", + "@stacks/transactions": "^6.2.0", "jsontokens": "^4.0.1", "schema-inspector": "^2.0.2", "zone-file": "^2.0.0-beta.3" } }, "@stacks/transactions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.1.1.tgz", - "integrity": "sha512-RyM4Y+UWXpz/seMa9B6MDqpneW+PFdcHIeF36p8X+N1b5yBJardg6LdI7VWn5KG4hKofYttwaVTIjotKYGRIRw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.2.0.tgz", + "integrity": "sha512-FUqcCNhCC5VlPvfUeeC6TB0gUifFj06E9xlWKbi2jB+oObh6XNBu4fS6I4EKxF1tEtUX9RNXcVbKDgMlX38syw==", "requires": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -26072,9 +26107,9 @@ } }, "sha256-uint8array": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.3.tgz", - "integrity": "sha512-SFTs87RfXVulKrhhP6B5/qcFruOKQZaKf6jY9V4PJ7NOG0qIlQP6XL4pQq5xagsuP/Wd55S7tUBJpRajEsDUEQ==" + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.5.tgz", + "integrity": "sha512-KjYP8M6y8VvV62lnSSZwzutuwqphVOOVQamP+GmmClZcYhbq1HSIw/M2tlKgHndiaIwe3tFG5X38YYsQfUdItw==" }, "shallow-clone": { "version": "3.0.1", diff --git a/package.json b/package.json index 3cd4b3e95..119f2cccb 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "private": true, "dependencies": { "@react-spring/web": "^9.6.1", - "@secretkeylabs/xverse-core": "0.7.3", + "@secretkeylabs/xverse-core": "0.8.0", "@stacks/connect": "^6.10.2", "@stacks/encryption": "4.3.5", "@stacks/stacks-blockchain-api-types": "^6.1.1", diff --git a/src/app/components/AlertMessage/index.tsx b/src/app/components/AlertMessage/index.tsx index 893c86b37..4cc02d33b 100644 --- a/src/app/components/AlertMessage/index.tsx +++ b/src/app/components/AlertMessage/index.tsx @@ -11,7 +11,7 @@ const Container = styled.div((props) => ({ transform: 'translate(-50%, -50%)', width: 312, borderRadius: 12, - zIndex: 2000, + zIndex: 16000, background: props.theme.colors.background.elevation2, filter: 'drop-shadow(0px 16px 36px rgba(0, 0, 0, 0.5))', })); diff --git a/src/app/components/infoContainer/index.tsx b/src/app/components/infoContainer/index.tsx index 822392060..1ef1d73ca 100644 --- a/src/app/components/infoContainer/index.tsx +++ b/src/app/components/infoContainer/index.tsx @@ -1,7 +1,8 @@ import styled from 'styled-components'; import InfoIcon from '@assets/img/info.svg'; +import WarningIcon from '@assets/img/Warning.svg'; -const Container = styled.div((props) => ({ +const Container = styled.div<{ type: 'Info' | 'Warning' | undefined }>((props) => ({ display: 'flex', flexDirection: 'row', borderRadius: 12, @@ -9,7 +10,9 @@ const Container = styled.div((props) => ({ backgroundColor: 'transparent', padding: props.theme.spacing(8), marginBottom: props.theme.spacing(6), - border: '1px solid rgba(255, 255, 255, 0.2)', + border: `1px solid ${ + props.type === 'Warning' ? props.theme.colors.feedback.error_700 : 'rgba(255, 255, 255, 0.2)' + }`, })); const TextContainer = styled.div((props) => ({ @@ -38,20 +41,22 @@ const Text = styled.h1((props) => ({ interface Props { titleText?: string; bodyText: string; + type?: 'Info' | 'Warning'; } -function InfoContainer({ titleText, bodyText }: Props) { +function InfoContainer({ titleText, bodyText, type }: Props) { return ( - - alert + + alert {titleText ? ( <> {titleText} {bodyText} - ) - : {bodyText}} + ) : ( + {bodyText} + )} ); diff --git a/src/app/components/sendForm/index.tsx b/src/app/components/sendForm/index.tsx index 2bc4bd8fe..18d3340ff 100644 --- a/src/app/components/sendForm/index.tsx +++ b/src/app/components/sendForm/index.tsx @@ -53,6 +53,10 @@ const Container = styled.div((props) => ({ marginTop: props.theme.spacing(16), })); +const OrdinalInfoContainer = styled.div((props) => ({ + marginTop: props.theme.spacing(6), +})); + const ErrorContainer = styled.div((props) => ({ marginTop: props.theme.spacing(3), })); @@ -329,18 +333,18 @@ function SendForm({ - {associatedAddress && currencyType !== 'BTC' && ( + {associatedAddress && currencyType !== 'BTC' && currencyType !== 'Ordinal' && ( <> {t('ASSOCIATED_ADDRESS')} {associatedAddress} )} - {associatedBnsName && currencyType !== 'BTC' && ( + {associatedBnsName && currencyType !== 'BTC' && currencyType !== 'Ordinal' && ( <> {t('ASSOCIATED_BNS_DOMAIN')} {associatedBnsName} @@ -379,7 +383,7 @@ function SendForm({ return ( <> - {currencyType !== 'NFT' && ( + {currencyType !== 'NFT' && currencyType !== 'Ordinal' && ( {addressError} - {currencyType !== 'BTC' && currencyType !== 'NFT' && !hideMemo && ( + {currencyType !== 'BTC' && currencyType !== 'NFT' && currencyType !== 'Ordinal' && !hideMemo && ( <> {t('MEMO')} @@ -419,6 +423,13 @@ function SendForm({ )} + { + currencyType === 'Ordinal' && ( + + + + ) + } diff --git a/src/app/components/transactionSetting/index.tsx b/src/app/components/transactionSetting/index.tsx index 0daff32a1..652a878ce 100644 --- a/src/app/components/transactionSetting/index.tsx +++ b/src/app/components/transactionSetting/index.tsx @@ -16,8 +16,8 @@ import { } from '@secretkeylabs/xverse-core/currency'; import { useSelector } from 'react-redux'; import { StoreState } from '@stores/index'; -import { getBtcFees, isCustomFeesAllowed } from '@secretkeylabs/xverse-core/transactions/btc'; -import { btcToSats, ErrorCodes } from '@secretkeylabs/xverse-core'; +import { getBtcFees, getBtcFeesForOrdinalSend, isCustomFeesAllowed } from '@secretkeylabs/xverse-core/transactions/btc'; +import { btcToSats, BtcUtxoDataResponse, ErrorCodes } from '@secretkeylabs/xverse-core'; const Text = styled.h1((props) => ({ ...props.theme.body_medium_m, @@ -141,8 +141,9 @@ interface Props { type?: TxType; btcRecepientAddress?: string; amount?: BigNumber; + ordinalTxUtxo?: BtcUtxoDataResponse; } -type TxType = 'STX' | 'BTC'; +type TxType = 'STX' | 'BTC' | 'Ordinals'; type FeeModeType = 'low' | 'standard' | 'high' | 'custom'; function TransactionSettingAlert({ @@ -158,12 +159,14 @@ function TransactionSettingAlert({ type = 'STX', btcRecepientAddress, amount, + ordinalTxUtxo, }:Props) { const { t } = useTranslation('translation'); const [feeInput, setFeeInput] = useState(fee); const theme = useTheme(); const [nonceInput, setNonceInput] = useState < string | undefined >(nonce); const [error, setError] = useState(''); + const [isLoading, setIsLoading] = useState(loading); const [selectedOption, setSelectedOption] = useState({ label: t('TRANSACTION_SETTING.STANDARD'), value: 'standard', @@ -270,24 +273,39 @@ function TransactionSettingAlert({ } }, [selectedOption]); - const modifyBtcFees = async (mode: SingleValue<{ label: string; value: string; }>) => { + const modifyFees = async (mode: SingleValue<{ label: string; value: string; }>) => { try { setSelectedOption(mode!); + setIsLoading(true); if (mode?.value === 'custom') inputRef?.current?.focus(); - else if (amount && selectedAccount && btcRecepientAddress) { - const btcFee = await getBtcFees( - [ - { - address: btcRecepientAddress, - amountSats: btcToSats(new BigNumber(amount)), - }, - ], - btcAddress, - network.type, - mode?.value, - ); - setFeeInput(btcFee.toString()); + else if (type === 'BTC') { + if (amount && selectedAccount && btcRecepientAddress) { + const btcFee = await getBtcFees( + [ + { + address: btcRecepientAddress, + amountSats: btcToSats(new BigNumber(amount)), + }, + ], + btcAddress, + network.type, + mode?.value, + ); + setFeeInput(btcFee.toString()); + } + } else if (type === 'Ordinals') { + if (btcRecepientAddress && ordinalTxUtxo) { + const txFees = await getBtcFeesForOrdinalSend( + btcRecepientAddress, + ordinalTxUtxo, + btcAddress, + network.type, + mode?.value, + ); + setFeeInput(txFees.toString()); + } } + setIsLoading(false); } catch (err: any) { if (Number(error) === ErrorCodes.InSufficientBalance) { setError(t('TX_ERRORS.INSUFFICIENT_BALANCE')); } else setError(error.toString()); } @@ -362,7 +380,7 @@ function TransactionSettingAlert({ setFeeInput(e.target.value); }; - const onFeeOptionChange = (value: { label: string; value: string } | null) => (type === 'STX' ? modifyStxFees(value) : modifyBtcFees(value)); + const onFeeOptionChange = (value: { label: string; value: string } | null) => (type === 'STX' ? modifyStxFees(value) : modifyFees(value)); const editFeesSection = ( @@ -422,8 +440,8 @@ function TransactionSettingAlert({ diff --git a/src/app/hooks/useTextOrdinalContent.ts b/src/app/hooks/useTextOrdinalContent.ts index 8449167ca..bc8915ab3 100644 --- a/src/app/hooks/useTextOrdinalContent.ts +++ b/src/app/hooks/useTextOrdinalContent.ts @@ -4,14 +4,18 @@ import { useEffect, useState } from 'react'; const useTextOrdinalContent = (ordinal: OrdinalInfo) => { const [textContent, setTextContent] = useState(''); - const url = `https://gammaordinals.com${ordinal?.metadata.content}`; useEffect(() => { - (async () => { - if (ordinal?.metadata['content type'].includes('text')) { - const response = await getTextOrdinalContent(url); setTextContent(response ?? ''); - } - })(); + if(ordinal) { + const url = `https://gammaordinals.com${ordinal?.metadata.content}`; + + (async () => { + if (ordinal?.metadata['content type'].startsWith('text/plain')) { + const response: string = await getTextOrdinalContent(url); + setTextContent(response ?? ''); + } + })(); + } }, [ordinal]); return textContent.toString(); diff --git a/src/app/routes/index.tsx b/src/app/routes/index.tsx index f9fb02564..65e4016ad 100644 --- a/src/app/routes/index.tsx +++ b/src/app/routes/index.tsx @@ -40,6 +40,8 @@ import SignatureRequest from '@screens/signatureRequest'; import TransactionRequest from '@screens/transactionRequest'; import ErrorBoundary from '@screens/error'; import OrdinalDetailScreen from '@screens/ordinalDetail'; +import SendOrdinal from '@screens/sendOrdinal'; +import ConfirmOrdinalTransaction from '@screens/confirmOrdinalTransaction'; const router = createHashRouter([ { @@ -195,6 +197,14 @@ const router = createHashRouter([ ), }, + { + path: 'send-ordinal', + element: ( + + + + ), + }, ], }, { @@ -226,6 +236,18 @@ const router = createHashRouter([ path: 'confirm-nft-tx/:id', element: , }, + { + path: 'confirm-ordinal-tx/:id', + element: , + }, + { + path: 'nft-dashboard/ordinal-detail/:id/send-ordinal', + element: ( + + + + ), + }, ], }, ]); diff --git a/src/app/screens/confirmNftTransaction/index.tsx b/src/app/screens/confirmNftTransaction/index.tsx index 60ea179c8..43e859540 100644 --- a/src/app/screens/confirmNftTransaction/index.tsx +++ b/src/app/screens/confirmNftTransaction/index.tsx @@ -149,6 +149,7 @@ function ConfirmNftTransaction() { txid: stxTxBroadcastData, currency: 'STX', error: '', + isNft: true, }, }); setTimeout(() => { @@ -164,6 +165,7 @@ function ConfirmNftTransaction() { txid: '', currency: 'STX', error: txError.toString(), + isNft: true, }, }); } diff --git a/src/app/screens/confirmOrdinalTransaction/index.tsx b/src/app/screens/confirmOrdinalTransaction/index.tsx new file mode 100644 index 000000000..a21ec379e --- /dev/null +++ b/src/app/screens/confirmOrdinalTransaction/index.tsx @@ -0,0 +1,247 @@ +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; +import { useMutation } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import ArrowLeft from '@assets/img/dashboard/arrow_left.svg'; +import Seperator from '@components/seperator'; +import { StoreState } from '@stores/index'; +import BottomBar from '@components/tabBar'; +import { fetchBtcWalletDataRequestAction } from '@stores/wallet/actions/actionCreators'; +import RecipientAddressView from '@components/recipinetAddressView'; +import useNftDataSelector from '@hooks/useNftDataSelector'; +import AccountHeaderComponent from '@components/accountHeader'; +import TopRow from '@components/topRow'; +import ConfirmBtcTransactionComponent from '@screens/confrimBtcTransaction/confirmBtcTransactionComponent'; +import { broadcastRawBtcOrdinalTransaction } from '@secretkeylabs/xverse-core'; +import OrdinalImage from '@screens/ordinals/ordinalImage'; + +const ScrollContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + overflow-y: auto; + &::-webkit-scrollbar { + display: none; + } + height: 600px; + width: 360px; + margin: auto; +`; + +const InfoContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'column', + marginTop: props.theme.spacing(12), +})); + +const TitleText = styled.h1((props) => ({ + ...props.theme.headline_category_s, + color: props.theme.colors.white['400'], + textTransform: 'uppercase', +})); + +const ButtonContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'row', + marginLeft: '15%', + marginTop: props.theme.spacing(40), +})); + +const Button = styled.button((props) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + borderRadius: props.theme.radius(1), + backgroundColor: 'transparent', + opacity: 0.8, + marginTop: props.theme.spacing(5), +})); + +const ButtonText = styled.div((props) => ({ + ...props.theme.body_xs, + fontWeight: 400, + fontSize: 14, + color: props.theme.colors.white['0'], + textAlign: 'center', +})); + +const ButtonImage = styled.img((props) => ({ + marginRight: props.theme.spacing(3), + alignSelf: 'center', + transform: 'all', +})); + +const IndicationText = styled.h1((props) => ({ + ...props.theme.headline_category_s, + color: props.theme.colors.white['400'], + textTransform: 'uppercase', + fontSize: 14, +})); + +const ValueText = styled.h1((props) => ({ + ...props.theme.body_m, + marginTop: props.theme.spacing(2), + wordBreak: 'break-all', +})); + +const BottomBarContainer = styled.h1((props) => ({ + marginTop: props.theme.spacing(3), +})); + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', +}); + +const NFtContainer = styled.div((props) => ({ + maxWidth: 120, + maxHeight: 120, + width: '60%', + display: 'flex', + aspectRatio: 1, + justifyContent: 'center', + alignItems: 'center', + borderRadius: 8, + padding: props.theme.spacing(5), + marginBottom: props.theme.spacing(6), +})); + +const OrdinalInscriptionNumber = styled.h1((props) => ({ + ...props.theme.headline_s, + color: props.theme.colors.white['0'], + textAlign: 'center', +})); + +function ConfirmOrdinalTransaction() { + const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); + const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; + const navigate = useNavigate(); + const dispatch = useDispatch(); + const { + network, btcAddress, stxBtcRate, btcFiatRate, + } = useSelector( + (state: StoreState) => state.walletState, + ); + const [recipientAddress, setRecipientAddress] = useState(''); + const location = useLocation(); + const { + fee, amount, signedTxHex, ordinalUtxo, + } = location.state; + const { + isLoading, + error: txError, + data: btcTxBroadcastData, + mutate, + } = useMutation( + async ({ signedTx }) => broadcastRawBtcOrdinalTransaction(signedTx, network.type), + ); + const { id } = useParams(); + const { ordinalsData } = useNftDataSelector(); + const ordinalId = id!.split('::'); + const ordinal = ordinalsData.find((inscription) => inscription?.metadata?.id === ordinalId[0]); + + useEffect(() => { + setRecipientAddress(location.state.recipientAddress); + }, [location]); + + useEffect(() => { + if (btcTxBroadcastData) { + navigate('/tx-status', { + state: { + txid: btcTxBroadcastData, + currency: 'BTC', + error: '', + isOrdinal: true, + }, + }); + setTimeout(() => { + dispatch( + fetchBtcWalletDataRequestAction(btcAddress, network.type, stxBtcRate, btcFiatRate), + ); + }, 1000); + } + }, [btcTxBroadcastData]); + + useEffect(() => { + if (txError) { + navigate('/tx-status', { + state: { + txid: '', + currency: 'BTC', + error: txError.toString(), + isOrdinal: true, + }, + }); + } + }, [txError]); + + const networkInfoSection = ( + + {t('NETWORK')} + {network.type} + + ); + + const handleOnConfirmClick = (txHex: string) => { + mutate({ signedTx: txHex }); + }; + + const handleOnCancelClick = () => { + navigate(-1); + }; + + return ( + <> + {isGalleryOpen && ( + <> + + + + + + )} + + + + + + + {ordinal?.inscriptionNumber} + + + {networkInfoSection} + + + + {!isGalleryOpen + && ( + + + + )} + + + + ); +} +export default ConfirmOrdinalTransaction; diff --git a/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx b/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx index 1254ae99f..5c6af734d 100644 --- a/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx +++ b/src/app/screens/confrimBtcTransaction/confirmBtcTransactionComponent/index.tsx @@ -10,10 +10,12 @@ import { useSelector } from 'react-redux'; import { StoreState } from '@stores/index'; import { signBtcTransaction } from '@secretkeylabs/xverse-core/transactions'; import { useMutation } from '@tanstack/react-query'; -import { Recipient, SignedBtcTx } from '@secretkeylabs/xverse-core/transactions/btc'; +import { Recipient, SignedBtcTx, signOrdinalSendTransaction } from '@secretkeylabs/xverse-core/transactions/btc'; import TransferAmountView from '@components/transferAmountView'; import TransferFeeView from '@components/transferFeeView'; -import { btcToSats, ErrorCodes, ResponseError } from '@secretkeylabs/xverse-core'; +import { + btcToSats, BtcUtxoDataResponse, ErrorCodes, ResponseError, +} from '@secretkeylabs/xverse-core'; const Container = styled.div((props) => ({ display: 'flex', @@ -29,8 +31,7 @@ const ButtonContainer = styled.div((props) => ({ flexDirection: 'row', marginLeft: props.theme.spacing(8), marginRight: props.theme.spacing(8), - marginBottom: props.theme.spacing(20), - marginTop: props.theme.spacing(5), + marginBottom: props.theme.spacing(5), })); const TransparentButtonContainer = styled.div((props) => ({ @@ -79,6 +80,7 @@ interface Props { amount: BigNumber; recipientAddress: string; signedTxHex: string; + ordinalTxUtxo?: BtcUtxoDataResponse; onConfirmClick: (signedTxHex: string) => void; onCancelClick: () => void; onBackButtonClick: () => void; @@ -91,11 +93,14 @@ function ConfirmBtcTransactionComponent({ amount, recipientAddress, signedTxHex, + ordinalTxUtxo, onConfirmClick, onCancelClick, onBackButtonClick, }: Props) { const { t } = useTranslation('translation'); + const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; + const [loading, setLoading] = useState(false); const [openTransactionSettingModal, setOpenTransactionSettingModal] = useState(false); const { btcAddress, selectedAccount, seedPhrase, network, @@ -125,13 +130,40 @@ function ConfirmBtcTransactionComponent({ ), ); + const { + isLoading: isLoadingOrdData, + data: ordinalData, + error: ordinalError, + mutate: ordinalMutate, + } = useMutation(async (txFee) => { + const signedTx = await signOrdinalSendTransaction( + recipientAddress, + ordinalTxUtxo!, + btcAddress, + Number(selectedAccount?.id), + seedPhrase, + network.type, + new BigNumber(txFee), + ); + return signedTx; + }); + useEffect(() => { if (data) { setCurrentFee(data.fee); setSignedTx(data.signedTx); + setOpenTransactionSettingModal(false); } }, [data]); + useEffect(() => { + if (ordinalData) { + setCurrentFee(ordinalData.fee); + setSignedTx(ordinalData.signedTx); + setOpenTransactionSettingModal(false); + } + }, [ordinalData]); + const onAdvancedSettingClick = () => { setOpenTransactionSettingModal(true); }; @@ -141,7 +173,6 @@ function ConfirmBtcTransactionComponent({ }; const onApplyClick = (modifiedFee: string) => { - setOpenTransactionSettingModal(false); setCurrentFee(new BigNumber(modifiedFee)); const recipients: Recipient[] = [ { @@ -149,7 +180,10 @@ function ConfirmBtcTransactionComponent({ amountSats: btcToSats(new BigNumber(amount)), }, ]; - mutate({ recipients, txFee: modifiedFee }); + + if (ordinalTxUtxo) ordinalMutate(modifiedFee); + else mutate({ recipients, txFee: modifiedFee }); + setLoading(true); }; const handleOnConfirmClick = () => { @@ -158,6 +192,7 @@ function ConfirmBtcTransactionComponent({ useEffect(() => { if (recipientAddress && amount && txError) { + setOpenTransactionSettingModal(false); if (Number(txError) === ErrorCodes.InSufficientBalance) { setError(t('TX_ERRORS.INSUFFICIENT_BALANCE')); } else if (Number(txError) === ErrorCodes.InSufficientBalanceWithTxFee) { @@ -166,11 +201,22 @@ function ConfirmBtcTransactionComponent({ } }, [txError]); + useEffect(() => { + if (recipientAddress && amount && ordinalError) { + setOpenTransactionSettingModal(false); + if (Number(txError) === ErrorCodes.InSufficientBalance) { + setError(t('TX_ERRORS.INSUFFICIENT_BALANCE')); + } else if (Number(txError) === ErrorCodes.InSufficientBalanceWithTxFee) { + setError(t('TX_ERRORS.INSUFFICIENT_BALANCE_FEES')); + } else setError(ordinalError.toString()); + } + }, [ordinalError]); + return ( <> - + {!isGalleryOpen && } - + {amount && } {children} - {ordinalData?.inscriptionNumber ?? t('INSCRIPTION')} + + {ordinalData?.inscriptionNumber ?? t('INSCRIPTION')} + - + @@ -330,36 +328,38 @@ function OrdinalDetailScreen() { {t('DESCRIPTION')} - {notSupportedOrdinal && ( - - )} + {notSupportedOrdinal && } {ordinalData?.metadata['content length'] && ( - + )} {ordinalData?.metadata['content type'] && ( - + )} {ordinalData?.metadata['output value'] && ( - + + )} + {ordinalData?.metadata.timestamp && ( + )} - {ordinalData?.metadata.timestamp && } {ordinalData?.metadata['genesis height'] && ( - + + )} + {ordinalData?.metadata.location && ( + )} - {ordinalData?.metadata.location && } @@ -373,13 +373,6 @@ function OrdinalDetailScreen() { )} - {showSendOridnalsAlert && ( - - )} {isGalleryOpen ? galleryView : extensionView} {!isGalleryOpen && ( diff --git a/src/app/screens/ordinals/ordinalImage.tsx b/src/app/screens/ordinals/ordinalImage.tsx index ee3cf52dd..00702f713 100644 --- a/src/app/screens/ordinals/ordinalImage.tsx +++ b/src/app/screens/ordinals/ordinalImage.tsx @@ -21,11 +21,15 @@ const ImageContainer = styled.div((props) => ({ alignItems: 'center', width: '100%', flex: 1, - height: props.isGalleryOpen ? '100%' : 156, + height: props.isGalleryOpen ? 300 : 150, + minHeight: props.isGalleryOpen ? 300 : 150, + maxHeight: props.isGalleryOpen ? 300 : 150, overflow: 'hidden', position: 'relative', fontSize: '3em', wordWrap: 'break-word', + backgroundColor: props.isGalleryOpen ? 'transparent' : '#1b1e2b', + borderRadius: 8 })); const ButtonIcon = styled.img({ @@ -93,7 +97,7 @@ function OrdinalImage({ ordinal, isNftDashboard = false, inNftDetail = false }: width="100%" placeholder={( - + )} src={getFetchableUrl(`https://gammaordinals.com${ordinal?.metadata.content}`, 'http')} diff --git a/src/app/screens/sendOrdinal/index.tsx b/src/app/screens/sendOrdinal/index.tsx new file mode 100644 index 000000000..000c5cf48 --- /dev/null +++ b/src/app/screens/sendOrdinal/index.tsx @@ -0,0 +1,265 @@ +import styled from 'styled-components'; +import { useTranslation } from 'react-i18next'; +import { useEffect, useState } from 'react'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { + BtcOrdinal, ErrorCodes, OrdinalInfo, ResponseError, +} from '@secretkeylabs/xverse-core/types'; +import { validateBtcAddress } from '@secretkeylabs/xverse-core/wallet'; +import { getOrdinalsByAddress } from '@secretkeylabs/xverse-core/api'; +import { + SignedBtcTx, + signOrdinalSendTransaction, + getBtcFeesForOrdinalSend, +} from '@secretkeylabs/xverse-core/transactions/btc'; +import useNftDataSelector from '@hooks/useNftDataSelector'; +import useWalletSelector from '@hooks/useWalletSelector'; +import SendForm from '@components/sendForm'; +import TopRow from '@components/topRow'; +import BottomBar from '@components/tabBar'; +import AccountHeaderComponent from '@components/accountHeader'; +import OrdinalImage from '@screens/ordinals/ordinalImage'; +import ArrowLeft from '@assets/img/dashboard/arrow_left.svg'; +import { BtcUtxoDataResponse, getBtcFiatEquivalent } from '@secretkeylabs/xverse-core'; + +const ScrollContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; + overflow-y: auto; + &::-webkit-scrollbar { + display: none; + } + width: 360px; + margin: auto; +`; + +const Container = styled.div({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + flex: 1, +}); + +const NFtContainer = styled.div((props) => ({ + maxHeight: 148, + width: 148, + display: 'flex', + aspectRatio: 1, + justifyContent: 'center', + alignItems: 'center', + borderRadius: 8, + marginTop: props.theme.spacing(16), + marginBottom: props.theme.spacing(12), +})); + +const OrdinalInscriptionNumber = styled.h1((props) => ({ + ...props.theme.headline_s, + color: props.theme.colors.white['0'], + textAlign: 'center', +})); + +const BottomBarContainer = styled.div({ + marginTop: 'auto', +}); + +const ButtonContainer = styled.div((props) => ({ + display: 'flex', + flexDirection: 'row', + marginLeft: '15%', + marginTop: props.theme.spacing(40), +})); + +const Button = styled.button((props) => ({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-end', + alignItems: 'center', + borderRadius: props.theme.radius(1), + backgroundColor: 'transparent', + opacity: 0.8, + marginTop: props.theme.spacing(5), +})); + +const ButtonText = styled.div((props) => ({ + ...props.theme.body_xs, + fontWeight: 400, + fontSize: 14, + color: props.theme.colors.white['0'], + textAlign: 'center', +})); + +const ButtonImage = styled.img((props) => ({ + marginRight: props.theme.spacing(3), + alignSelf: 'center', + transform: 'all', +})); + +function SendOrdinal() { + const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); + const navigate = useNavigate(); + const { id } = useParams(); + const location = useLocation(); + const { + network, ordinalsAddress, btcAddress, selectedAccount, seedPhrase, btcFiatRate, + } = useWalletSelector(); + const { ordinalsData } = useNftDataSelector(); + const [ordinal, setOrdinal] = useState(undefined); + const [ordinalUtxo, setOrdinalUtxo] = useState(undefined); + const [error, setError] = useState(''); + const [recipientAddress, setRecipientAddress] = useState(''); + + let address: string | undefined; + + if (location.state) { + address = location.state.recipientAddress; + } + + const ordinalIdDetails = id!.split('::'); + + const isGalleryOpen: boolean = document.documentElement.clientWidth > 360; + + useEffect(() => { + const data = ordinalsData.find( + (inscription) => inscription?.metadata?.id === ordinalIdDetails[0], + ); + if (data) { + setOrdinal(data); + } + }, []); + + function fetchOrdinals(): Promise { + return getOrdinalsByAddress(ordinalsAddress); + } + + const { data: ordinals } = useQuery({ + queryKey: [`ordinals-${ordinalsAddress}`], + queryFn: fetchOrdinals, + }); + + const { + isLoading, + data, + error: txError, + mutate, + } = useMutation(async (recepient) => { + const ordinalUtx = ordinals?.find((inscription) => inscription.id === ordinalIdDetails[0])?.utxo; + setOrdinalUtxo(ordinalUtx); + if (ordinalUtx) { + const txFees = await getBtcFeesForOrdinalSend( + recepient, + ordinalUtx, + btcAddress, + network.type, + ); + const signedTx = await signOrdinalSendTransaction( + recepient, + ordinalUtx, + btcAddress, + Number(selectedAccount?.id), + seedPhrase, + network.type, + txFees, + ); + return signedTx; + } + }); + + useEffect(() => { + if (txError) { + if (Number(txError) === ErrorCodes.InSufficientBalance) { + setError(t('ERRORS.INSUFFICIENT_BALANCE')); + } else if (Number(txError) === ErrorCodes.InSufficientBalanceWithTxFee) { + setError(t('ERRORS.INSUFFICIENT_BALANCE_FEES')); + } else setError(txError.toString()); + } + }, [txError]); + + useEffect(() => { + if (data) { + navigate(`/confirm-ordinal-tx/${id}`, { + state: { + signedTxHex: data.signedTx, + recipientAddress, + fee: data.fee, + fiatFee: getBtcFiatEquivalent(data.fee, btcFiatRate), + total: data.total, + fiatTotal: getBtcFiatEquivalent(data.total, btcFiatRate), + ordinalUtxo, + }, + }); + } + }, [data]); + + const handleBackButtonClick = () => { + navigate(-1); + }; + + function validateFields(associatedAddress: string): boolean { + if (!associatedAddress) { + setError(t('ERRORS.ADDRESS_REQUIRED')); + return false; + } + + if (!validateBtcAddress({ btcAddress: associatedAddress, network: network.type })) { + setError(t('ERRORS.ADDRESS_INVALID')); + return false; + } + + if (associatedAddress === ordinalsAddress) { + setError(t('ERRORS.SEND_TO_SELF')); + return false; + } + + return true; + } + + const onPressNext = async (associatedAddress: string) => { + setRecipientAddress(associatedAddress); + if (validateFields(associatedAddress)) { + mutate(associatedAddress); + } + }; + return ( + <> + {isGalleryOpen && ( + <> + + + + + + )} + + {!isGalleryOpen && ( + + )} + + + + + + {ordinal?.inscriptionNumber} + + + {!isGalleryOpen && } + + + ); +} + +export default SendOrdinal; diff --git a/src/app/screens/transactionStatus/index.tsx b/src/app/screens/transactionStatus/index.tsx index eac6d2347..429971521 100644 --- a/src/app/screens/transactionStatus/index.tsx +++ b/src/app/screens/transactionStatus/index.tsx @@ -106,15 +106,12 @@ const ButtonText = styled.h1((props) => ({ const ButtonImage = styled.img((props) => ({ marginRight: props.theme.spacing(3), - alignSelf: 'center', - transform: 'all', })); const Button = styled.button((props) => ({ display: 'flex', flexDirection: 'row', backgroundColor: 'transparent', - marginTop: props.theme.spacing(2), marginLeft: props.theme.spacing(3), })); @@ -124,7 +121,7 @@ function TransactionStatus() { const location = useLocation(); const { network } = useWalletSelector(); const { - txid, currency, error, sponsored, browserTx, + txid, currency, error, sponsored, browserTx, isOrdinal, isNft, } = location.state; const renderTransactionSuccessStatus = ( @@ -155,6 +152,8 @@ function TransactionStatus() { const onCloseClick = () => { if (browserTx) window.close(); + else if (isOrdinal) navigate(-4); + else if (isNft) navigate(-3); else navigate(-2); }; diff --git a/src/app/utils/constants.ts b/src/app/utils/constants.ts index adab908b6..a89da8e51 100644 --- a/src/app/utils/constants.ts +++ b/src/app/utils/constants.ts @@ -18,7 +18,7 @@ export const MOON_PAY_API_KEY = 'pk_live_8YeOjOzFqHUG1qi2G6NPA4N1tZAWFihK'; export const BINANCE_URL = 'https://www.binancecnt.com/en/pre-connect'; export const BINANCE_MERCHANT_CODE = 'secret_key_labs'; -export type CurrencyTypes = 'STX' | 'BTC' | 'FT' | 'NFT'; +export type CurrencyTypes = 'STX' | 'BTC' | 'FT' | 'NFT' | 'Ordinal'; export enum LoaderSize { SMALLEST, SMALL, diff --git a/src/assets/img/Warning.svg b/src/assets/img/Warning.svg new file mode 100644 index 000000000..0b6f24502 --- /dev/null +++ b/src/assets/img/Warning.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/locales/en.json b/src/locales/en.json index 8080167a9..b4dd31578 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -49,7 +49,7 @@ "BALANCE": "Balance", "RECEPIENT": "Recipient", "RECEPIENT_PLACEHOLDER": "Address or .btc domain", - "BTC_RECEPIENT_PLACEHOLDER": "Address", + "BTC_RECEPIENT_PLACEHOLDER": "Enter Bitcoin address", "MEMO": "Add a memo (optional)", "MEMO_PLACEHOLDER": "Memo", "ASSOCIATED_ADDRESS": "Associated Address", @@ -72,10 +72,12 @@ "NFT_SEND_DETAIL": "This Nft is already sent and is in pending state" }, "SEND_NFT": "Send NFT", - "ASSOCIATED_BNS_DOMAIN": "Associated BNS Name" + "ASSOCIATED_BNS_DOMAIN": "Associated BNS Name", + "SEND_ORDINAL_TITLE": "Send Ordinal", + "SEND_ORDINAL_WALLET_WARNING": "You are about to transfer an Ordinal. Make sure the recipient wallet supports Ordinals." }, "CONFIRM_TRANSACTION": { - "SEND": "Send", + "SEND": "Confirm Transaction", "INDICATION": "You will send", "RECEPIENT_ADDRESS": "recipient address", "MEMO": "Attached memo", @@ -112,7 +114,7 @@ }, "TRANSACTION_STATUS": { "BROADCASTED": "Transaction Broadcasted", - "SUCCESS_MSG": "Your transaction have been successfully submitted and should appear in a few minutes.", + "SUCCESS_MSG": "Your transaction has been successfully submitted.", "SEE_ON": "See on", "STACKS_EXPLORER": "Stacks Explorer", "BITCOIN_EXPLORER": "Explorer",