diff --git a/package-lock.json b/package-lock.json index 09a463b8c..21d5ee8d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@phosphor-icons/react": "^2.0.10", "@react-spring/web": "^9.6.1", "@scure/btc-signer": "1.2.1", - "@secretkeylabs/xverse-core": "11.1.2", + "@secretkeylabs/xverse-core": "11.3.0", "@stacks/connect": "7.4.1", "@stacks/stacks-blockchain-api-types": "6.1.1", "@stacks/transactions": "6.9.0", @@ -514,9 +514,9 @@ } }, "node_modules/@bitcoinerlab/secp256k1": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz", - "integrity": "sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.1.1.tgz", + "integrity": "sha512-uhjW51WfVLpnHN7+G0saDcM/k9IqcyTbZ+bDgLF3AX8V/a3KXSE9vn7UPBrcdU72tp0J4YPR7BHp2m7MLAZ/1Q==", "dependencies": { "@noble/hashes": "^1.1.5", "@noble/secp256k1": "^1.7.1" @@ -1739,9 +1739,9 @@ } }, "node_modules/@secretkeylabs/xverse-core": { - "version": "11.1.2", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/11.1.2/1c26c961697248d6ee60b58d04006eb886ea3a87", - "integrity": "sha512-6uCpT5zwbwC85wybT1kGXeaTEeiYQ9r/yq4twg2qwu0ZGrJ33n+SFQ+2KAkvTsSpL1AJ/0cXQMJcGjMDuWpoVg==", + "version": "11.3.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/11.3.0/f64ea63481c407339f8cc6c179be2fd679afb65b", + "integrity": "sha512-sFyGWNtm2q3I/XqkCNUDbVR/A8HsqqSQaPx55yatUDJUWvcF10in9a182/HvefPQI2Ea5xOb+NsApnwKPPqLxw==", "license": "ISC", "dependencies": { "@bitcoinerlab/secp256k1": "^1.0.2", @@ -1753,6 +1753,7 @@ "@stacks/connect": "^7.4.1", "@stacks/encryption": "6.9.0", "@stacks/network": "6.8.1", + "@stacks/stacking": "^6.12.0", "@stacks/storage": "^6.9.0", "@stacks/transactions": "6.9.0", "@stacks/wallet-sdk": "^6.9.0", @@ -1767,11 +1768,13 @@ "bitcoinjs-lib": "^6.1.3", "bitcoinjs-message": "^2.2.0", "bn.js": "^5.1.3", + "bs58": "^5.0.0", "bs58check": "^3.0.1", "buffer": "6.0.3", "c32check": "^2.0.0", "ecdsa-sig-formatter": "^1.0.11", "ecpair": "^2.1.0", + "json-bigint": "^1.0.0", "jsontokens": "^4.0.1", "ledger-bitcoin": "^0.2.1", "process": "^0.11.10", @@ -1784,7 +1787,8 @@ }, "peerDependencies": { "bignumber.js": "^9.0.0", - "react": ">18.0.0" + "react": ">18.0.0", + "react-dom": ">18.0.0" } }, "node_modules/@secretkeylabs/xverse-core/node_modules/@types/node": { @@ -1810,14 +1814,14 @@ "dev": true }, "node_modules/@stacks/auth": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.10.0.tgz", - "integrity": "sha512-5/FdD1btPovJTckVfaNdD+J/JxtKwn1jVOcyiuDwOABMOMGykgH8ZdXc0Kho2POZoOwKTIrG8sYH5TolSoH7BA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.12.0.tgz", + "integrity": "sha512-ViSpeWNw1MJBtvykcPTcn9vTy9v8pK8jiQFjfOkiFQ0q+oEhIDQrtpN5OYtTh5HvoZk8du+43nIoUYUECNahHQ==", "dependencies": { "@stacks/common": "^6.10.0", - "@stacks/encryption": "^6.10.0", - "@stacks/network": "^6.10.0", - "@stacks/profile": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", + "@stacks/profile": "^6.12.0", "cross-fetch": "^3.1.5", "jsontokens": "^4.0.1" } @@ -1834,9 +1838,9 @@ ] }, "node_modules/@stacks/auth/node_modules/@stacks/encryption": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.11.0.tgz", - "integrity": "sha512-VfBkrwmCRppCasJo+R/hWfC7vgS6GmfPyoTeDsoYlfRRXz/auFbEdRaaruFPtAda/1nKdDOZ9UZEMOp5AIw0IQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", "dependencies": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -1850,9 +1854,9 @@ } }, "node_modules/@stacks/auth/node_modules/@stacks/network": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.10.0.tgz", - "integrity": "sha512-mbiZ8nlsyy77ndmBdaqhHXii22IFdK4ThRcOQs9j/O00DkAr04jCM4GV5Q+VLUnZ9OBoJq7yOV7Pf6jglh+0hw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "dependencies": { "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" @@ -1896,7 +1900,7 @@ "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", "@scure/bip39": "1.1.0", - "@stacks/common": "^6.10.0", + "@stacks/common": "^6.8.1", "@types/node": "^18.0.4", "base64-js": "^1.5.1", "bs58": "^5.0.0", @@ -1925,13 +1929,13 @@ } }, "node_modules/@stacks/profile": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.10.0.tgz", - "integrity": "sha512-n2H1Imuu7UfwIvUv7xgHiSb3CKfbP4H+Jzy1+w73njYX7glt60uQ1SjWef7gvMgMFQNTAPELqitweyA+UII6Hg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.12.0.tgz", + "integrity": "sha512-eY5IpX+GGHIcYLkivDmf3mkxDIgTQ+LhU8UG4jvOqDi+e1DvF9cIi3+reG5KVo0pQzqyT1OS7+16AwVcgp7/og==", "dependencies": { "@stacks/common": "^6.10.0", - "@stacks/network": "^6.10.0", - "@stacks/transactions": "^6.10.0", + "@stacks/network": "^6.11.3", + "@stacks/transactions": "^6.12.0", "jsontokens": "^4.0.1", "schema-inspector": "^2.0.2", "zone-file": "^2.0.0-beta.3" @@ -1949,23 +1953,102 @@ ] }, "node_modules/@stacks/profile/node_modules/@stacks/network": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.10.0.tgz", - "integrity": "sha512-mbiZ8nlsyy77ndmBdaqhHXii22IFdK4ThRcOQs9j/O00DkAr04jCM4GV5Q+VLUnZ9OBoJq7yOV7Pf6jglh+0hw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "dependencies": { "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" } }, "node_modules/@stacks/profile/node_modules/@stacks/transactions": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.11.0.tgz", - "integrity": "sha512-+zIDqn9j4H/+o1ER8C9rFpig1fyrQcj2hVGNIrp+YbpPyja+cxv3fPk6kI/gePzwggzxRgUkIWhBc+mZAXuXyQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", "dependencies": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", "@stacks/common": "^6.10.0", - "@stacks/network": "^6.10.0", + "@stacks/network": "^6.11.3", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/@stacks/stacking": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/stacking/-/stacking-6.12.0.tgz", + "integrity": "sha512-XBxwbaCGRPnjpjspb3CBXrlZl6xR+gghLMz9PQNPdpuIbBDFa0SGeHgqjtpVU+2DVL4UyBx8PVsAWtlssyVGng==", + "dependencies": { + "@scure/base": "1.1.1", + "@stacks/common": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", + "@stacks/stacks-blockchain-api-types": "^0.61.0", + "@stacks/transactions": "^6.12.0", + "bs58": "^5.0.0" + } + }, + "node_modules/@stacks/stacking/node_modules/@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@stacks/stacking/node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@stacks/stacking/node_modules/@stacks/encryption": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@scure/bip39": "1.1.0", + "@stacks/common": "^6.10.0", + "@types/node": "^18.0.4", + "base64-js": "^1.5.1", + "bs58": "^5.0.0", + "ripemd160-min": "^0.0.6", + "varuint-bitcoin": "^1.1.2" + } + }, + "node_modules/@stacks/stacking/node_modules/@stacks/network": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", + "dependencies": { + "@stacks/common": "^6.10.0", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/stacking/node_modules/@stacks/stacks-blockchain-api-types": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@stacks/stacks-blockchain-api-types/-/stacks-blockchain-api-types-0.61.0.tgz", + "integrity": "sha512-yPOfTUboo5eA9BZL/hqMcM71GstrFs9YWzOrJFPeP4cOO1wgYvAcckgBRbgiE3NqeX0A7SLZLDAXLZbATuRq9w==" + }, + "node_modules/@stacks/stacking/node_modules/@stacks/transactions": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.10.0", + "@stacks/network": "^6.11.3", "c32check": "^2.0.0", "lodash.clonedeep": "^4.5.0" } @@ -1976,14 +2059,14 @@ "integrity": "sha512-Mw5dBPx3DySPupwaq0iBdm1WdEVXIfhjUVaTjI2iSyzWz4Fgs3U7JCaAezLbgNu7Q69c/ZN4JUDWuo9FVjy7oA==" }, "node_modules/@stacks/storage": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.10.0.tgz", - "integrity": "sha512-DLinjJkCN9Q7Yu5yelcXfP89CIDZ4TqXisjJYRqRlFfQdoDWDFZT5vpM2C8U2xDmgzVxfjg90HmQpIjTeIMSnw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.12.0.tgz", + "integrity": "sha512-RiB8cfWKCm4LrDLXdjtRj4syzvpwmJ/6WKcF+1RHQBTyJerPgxoBn1NoKzjOcydQvVj1jpR/ccaS78xVxlMsTQ==", "dependencies": { - "@stacks/auth": "^6.10.0", + "@stacks/auth": "^6.12.0", "@stacks/common": "^6.10.0", - "@stacks/encryption": "^6.10.0", - "@stacks/network": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", "base64-js": "^1.5.1", "jsontokens": "^4.0.1" } @@ -2000,9 +2083,9 @@ ] }, "node_modules/@stacks/storage/node_modules/@stacks/encryption": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.11.0.tgz", - "integrity": "sha512-VfBkrwmCRppCasJo+R/hWfC7vgS6GmfPyoTeDsoYlfRRXz/auFbEdRaaruFPtAda/1nKdDOZ9UZEMOp5AIw0IQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", "dependencies": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -2016,9 +2099,9 @@ } }, "node_modules/@stacks/storage/node_modules/@stacks/network": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.10.0.tgz", - "integrity": "sha512-mbiZ8nlsyy77ndmBdaqhHXii22IFdK4ThRcOQs9j/O00DkAr04jCM4GV5Q+VLUnZ9OBoJq7yOV7Pf6jglh+0hw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "dependencies": { "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" @@ -2049,19 +2132,19 @@ ] }, "node_modules/@stacks/wallet-sdk": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@stacks/wallet-sdk/-/wallet-sdk-6.9.0.tgz", - "integrity": "sha512-cNp8gilFYovVgFYSd+g+2ybVgDcY0vshhi23lweeiTViGAogURfDvZgczwQ9yoYzdHA8vMGy5PxV5W9dbrrbcQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/wallet-sdk/-/wallet-sdk-6.12.0.tgz", + "integrity": "sha512-FhTjkDsuikAQ5VGhnqXG1DhncnX1TVO4rfJmbE/rJgkJw+6OJBJF7DrjIs6SCahVeY8FQloYxAQw+slnbddLBQ==", "dependencies": { "@scure/bip32": "1.1.3", "@scure/bip39": "1.1.0", - "@stacks/auth": "^6.9.0", - "@stacks/common": "^6.8.1", - "@stacks/encryption": "^6.9.0", - "@stacks/network": "^6.8.1", - "@stacks/profile": "^6.9.0", - "@stacks/storage": "^6.9.0", - "@stacks/transactions": "^6.9.0", + "@stacks/auth": "^6.12.0", + "@stacks/common": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", + "@stacks/profile": "^6.12.0", + "@stacks/storage": "^6.12.0", + "@stacks/transactions": "^6.12.0", "buffer": "^6.0.3", "c32check": "^2.0.0", "jsontokens": "^4.0.1", @@ -2069,6 +2152,55 @@ "zone-file": "^2.0.0-beta.3" } }, + "node_modules/@stacks/wallet-sdk/node_modules/@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@stacks/wallet-sdk/node_modules/@stacks/encryption": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@scure/bip39": "1.1.0", + "@stacks/common": "^6.10.0", + "@types/node": "^18.0.4", + "base64-js": "^1.5.1", + "bs58": "^5.0.0", + "ripemd160-min": "^0.0.6", + "varuint-bitcoin": "^1.1.2" + } + }, + "node_modules/@stacks/wallet-sdk/node_modules/@stacks/network": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", + "dependencies": { + "@stacks/common": "^6.10.0", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/wallet-sdk/node_modules/@stacks/transactions": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.10.0", + "@stacks/network": "^6.11.3", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + }, "node_modules/@stencil/core": { "version": "2.22.3", "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", @@ -4049,9 +4181,9 @@ } }, "node_modules/async-mutex": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", - "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", "dependencies": { "tslib": "^2.4.0" } @@ -9036,6 +9168,14 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -10012,9 +10152,9 @@ } }, "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==" + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" }, "node_modules/nano-time": { "version": "1.0.0", @@ -17278,9 +17418,9 @@ } }, "@bitcoinerlab/secp256k1": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz", - "integrity": "sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.1.1.tgz", + "integrity": "sha512-uhjW51WfVLpnHN7+G0saDcM/k9IqcyTbZ+bDgLF3AX8V/a3KXSE9vn7UPBrcdU72tp0J4YPR7BHp2m7MLAZ/1Q==", "requires": { "@noble/hashes": "^1.1.5", "@noble/secp256k1": "^1.7.1" @@ -18048,9 +18188,9 @@ } }, "@secretkeylabs/xverse-core": { - "version": "11.1.2", - "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/11.1.2/1c26c961697248d6ee60b58d04006eb886ea3a87", - "integrity": "sha512-6uCpT5zwbwC85wybT1kGXeaTEeiYQ9r/yq4twg2qwu0ZGrJ33n+SFQ+2KAkvTsSpL1AJ/0cXQMJcGjMDuWpoVg==", + "version": "11.3.0", + "resolved": "https://npm.pkg.github.com/download/@secretkeylabs/xverse-core/11.3.0/f64ea63481c407339f8cc6c179be2fd679afb65b", + "integrity": "sha512-sFyGWNtm2q3I/XqkCNUDbVR/A8HsqqSQaPx55yatUDJUWvcF10in9a182/HvefPQI2Ea5xOb+NsApnwKPPqLxw==", "requires": { "@bitcoinerlab/secp256k1": "^1.0.2", "@noble/curves": "^1.2.0", @@ -18061,6 +18201,7 @@ "@stacks/connect": "^7.4.1", "@stacks/encryption": "6.9.0", "@stacks/network": "6.8.1", + "@stacks/stacking": "^6.12.0", "@stacks/storage": "^6.9.0", "@stacks/transactions": "6.9.0", "@stacks/wallet-sdk": "^6.9.0", @@ -18075,11 +18216,13 @@ "bitcoinjs-lib": "^6.1.3", "bitcoinjs-message": "^2.2.0", "bn.js": "^5.1.3", + "bs58": "^5.0.0", "bs58check": "^3.0.1", "buffer": "6.0.3", "c32check": "^2.0.0", "ecdsa-sig-formatter": "^1.0.11", "ecpair": "^2.1.0", + "json-bigint": "^1.0.0", "jsontokens": "^4.0.1", "ledger-bitcoin": "^0.2.1", "process": "^0.11.10", @@ -18113,14 +18256,14 @@ "dev": true }, "@stacks/auth": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.10.0.tgz", - "integrity": "sha512-5/FdD1btPovJTckVfaNdD+J/JxtKwn1jVOcyiuDwOABMOMGykgH8ZdXc0Kho2POZoOwKTIrG8sYH5TolSoH7BA==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-6.12.0.tgz", + "integrity": "sha512-ViSpeWNw1MJBtvykcPTcn9vTy9v8pK8jiQFjfOkiFQ0q+oEhIDQrtpN5OYtTh5HvoZk8du+43nIoUYUECNahHQ==", "requires": { "@stacks/common": "^6.10.0", - "@stacks/encryption": "^6.10.0", - "@stacks/network": "^6.10.0", - "@stacks/profile": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", + "@stacks/profile": "^6.12.0", "cross-fetch": "^3.1.5", "jsontokens": "^4.0.1" }, @@ -18131,9 +18274,9 @@ "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==" }, "@stacks/encryption": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.11.0.tgz", - "integrity": "sha512-VfBkrwmCRppCasJo+R/hWfC7vgS6GmfPyoTeDsoYlfRRXz/auFbEdRaaruFPtAda/1nKdDOZ9UZEMOp5AIw0IQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", "requires": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -18147,9 +18290,9 @@ } }, "@stacks/network": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.10.0.tgz", - "integrity": "sha512-mbiZ8nlsyy77ndmBdaqhHXii22IFdK4ThRcOQs9j/O00DkAr04jCM4GV5Q+VLUnZ9OBoJq7yOV7Pf6jglh+0hw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "requires": { "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" @@ -18195,7 +18338,7 @@ "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", "@scure/bip39": "1.1.0", - "@stacks/common": "^6.10.0", + "@stacks/common": "^6.8.1", "@types/node": "^18.0.4", "base64-js": "^1.5.1", "bs58": "^5.0.0", @@ -18220,13 +18363,13 @@ } }, "@stacks/profile": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.10.0.tgz", - "integrity": "sha512-n2H1Imuu7UfwIvUv7xgHiSb3CKfbP4H+Jzy1+w73njYX7glt60uQ1SjWef7gvMgMFQNTAPELqitweyA+UII6Hg==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-6.12.0.tgz", + "integrity": "sha512-eY5IpX+GGHIcYLkivDmf3mkxDIgTQ+LhU8UG4jvOqDi+e1DvF9cIi3+reG5KVo0pQzqyT1OS7+16AwVcgp7/og==", "requires": { "@stacks/common": "^6.10.0", - "@stacks/network": "^6.10.0", - "@stacks/transactions": "^6.10.0", + "@stacks/network": "^6.11.3", + "@stacks/transactions": "^6.12.0", "jsontokens": "^4.0.1", "schema-inspector": "^2.0.2", "zone-file": "^2.0.0-beta.3" @@ -18238,23 +18381,92 @@ "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==" }, "@stacks/network": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.10.0.tgz", - "integrity": "sha512-mbiZ8nlsyy77ndmBdaqhHXii22IFdK4ThRcOQs9j/O00DkAr04jCM4GV5Q+VLUnZ9OBoJq7yOV7Pf6jglh+0hw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "requires": { "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" } }, "@stacks/transactions": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.11.0.tgz", - "integrity": "sha512-+zIDqn9j4H/+o1ER8C9rFpig1fyrQcj2hVGNIrp+YbpPyja+cxv3fPk6kI/gePzwggzxRgUkIWhBc+mZAXuXyQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", "requires": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", "@stacks/common": "^6.10.0", - "@stacks/network": "^6.10.0", + "@stacks/network": "^6.11.3", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + } + } + }, + "@stacks/stacking": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/stacking/-/stacking-6.12.0.tgz", + "integrity": "sha512-XBxwbaCGRPnjpjspb3CBXrlZl6xR+gghLMz9PQNPdpuIbBDFa0SGeHgqjtpVU+2DVL4UyBx8PVsAWtlssyVGng==", + "requires": { + "@scure/base": "1.1.1", + "@stacks/common": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", + "@stacks/stacks-blockchain-api-types": "^0.61.0", + "@stacks/transactions": "^6.12.0", + "bs58": "^5.0.0" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==" + }, + "@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==" + }, + "@stacks/encryption": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", + "requires": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@scure/bip39": "1.1.0", + "@stacks/common": "^6.10.0", + "@types/node": "^18.0.4", + "base64-js": "^1.5.1", + "bs58": "^5.0.0", + "ripemd160-min": "^0.0.6", + "varuint-bitcoin": "^1.1.2" + } + }, + "@stacks/network": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", + "requires": { + "@stacks/common": "^6.10.0", + "cross-fetch": "^3.1.5" + } + }, + "@stacks/stacks-blockchain-api-types": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@stacks/stacks-blockchain-api-types/-/stacks-blockchain-api-types-0.61.0.tgz", + "integrity": "sha512-yPOfTUboo5eA9BZL/hqMcM71GstrFs9YWzOrJFPeP4cOO1wgYvAcckgBRbgiE3NqeX0A7SLZLDAXLZbATuRq9w==" + }, + "@stacks/transactions": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", + "requires": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.10.0", + "@stacks/network": "^6.11.3", "c32check": "^2.0.0", "lodash.clonedeep": "^4.5.0" } @@ -18267,14 +18479,14 @@ "integrity": "sha512-Mw5dBPx3DySPupwaq0iBdm1WdEVXIfhjUVaTjI2iSyzWz4Fgs3U7JCaAezLbgNu7Q69c/ZN4JUDWuo9FVjy7oA==" }, "@stacks/storage": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.10.0.tgz", - "integrity": "sha512-DLinjJkCN9Q7Yu5yelcXfP89CIDZ4TqXisjJYRqRlFfQdoDWDFZT5vpM2C8U2xDmgzVxfjg90HmQpIjTeIMSnw==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/storage/-/storage-6.12.0.tgz", + "integrity": "sha512-RiB8cfWKCm4LrDLXdjtRj4syzvpwmJ/6WKcF+1RHQBTyJerPgxoBn1NoKzjOcydQvVj1jpR/ccaS78xVxlMsTQ==", "requires": { - "@stacks/auth": "^6.10.0", + "@stacks/auth": "^6.12.0", "@stacks/common": "^6.10.0", - "@stacks/encryption": "^6.10.0", - "@stacks/network": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", "base64-js": "^1.5.1", "jsontokens": "^4.0.1" }, @@ -18285,9 +18497,9 @@ "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==" }, "@stacks/encryption": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.11.0.tgz", - "integrity": "sha512-VfBkrwmCRppCasJo+R/hWfC7vgS6GmfPyoTeDsoYlfRRXz/auFbEdRaaruFPtAda/1nKdDOZ9UZEMOp5AIw0IQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", "requires": { "@noble/hashes": "1.1.5", "@noble/secp256k1": "1.7.1", @@ -18301,9 +18513,9 @@ } }, "@stacks/network": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.10.0.tgz", - "integrity": "sha512-mbiZ8nlsyy77ndmBdaqhHXii22IFdK4ThRcOQs9j/O00DkAr04jCM4GV5Q+VLUnZ9OBoJq7yOV7Pf6jglh+0hw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", "requires": { "@stacks/common": "^6.10.0", "cross-fetch": "^3.1.5" @@ -18332,24 +18544,69 @@ } }, "@stacks/wallet-sdk": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@stacks/wallet-sdk/-/wallet-sdk-6.9.0.tgz", - "integrity": "sha512-cNp8gilFYovVgFYSd+g+2ybVgDcY0vshhi23lweeiTViGAogURfDvZgczwQ9yoYzdHA8vMGy5PxV5W9dbrrbcQ==", + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/wallet-sdk/-/wallet-sdk-6.12.0.tgz", + "integrity": "sha512-FhTjkDsuikAQ5VGhnqXG1DhncnX1TVO4rfJmbE/rJgkJw+6OJBJF7DrjIs6SCahVeY8FQloYxAQw+slnbddLBQ==", "requires": { "@scure/bip32": "1.1.3", "@scure/bip39": "1.1.0", - "@stacks/auth": "^6.9.0", - "@stacks/common": "^6.8.1", - "@stacks/encryption": "^6.9.0", - "@stacks/network": "^6.8.1", - "@stacks/profile": "^6.9.0", - "@stacks/storage": "^6.9.0", - "@stacks/transactions": "^6.9.0", + "@stacks/auth": "^6.12.0", + "@stacks/common": "^6.10.0", + "@stacks/encryption": "^6.12.0", + "@stacks/network": "^6.11.3", + "@stacks/profile": "^6.12.0", + "@stacks/storage": "^6.12.0", + "@stacks/transactions": "^6.12.0", "buffer": "6.0.3", "c32check": "^2.0.0", "jsontokens": "^4.0.1", "triplesec": "^4.0.3", "zone-file": "^2.0.0-beta.3" + }, + "dependencies": { + "@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==" + }, + "@stacks/encryption": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-6.12.0.tgz", + "integrity": "sha512-CubE51pHrcxx3yA+xapevPgA9UDleIoEaUZ06/9uD91B42yvTg37HyS8t06rzukU9q+X7Cv2I/+vbuf4nJIo8g==", + "requires": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@scure/bip39": "1.1.0", + "@stacks/common": "^6.10.0", + "@types/node": "^18.0.4", + "base64-js": "^1.5.1", + "bs58": "^5.0.0", + "ripemd160-min": "^0.0.6", + "varuint-bitcoin": "^1.1.2" + } + }, + "@stacks/network": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.11.3.tgz", + "integrity": "sha512-c4ClCU/QUwuu8NbHtDKPJNa0M5YxauLN3vYaR0+S4awbhVIKFQSxirm9Q9ckV1WBh7FtD6u2S0x+tDQGAODjNg==", + "requires": { + "@stacks/common": "^6.10.0", + "cross-fetch": "^3.1.5" + } + }, + "@stacks/transactions": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.12.0.tgz", + "integrity": "sha512-gRP3SfTaAIoTdjMvOiLrMZb/senqB8JQlT5Y4C3/CiHhiprYwTx7TbOCSa7WsNOU99H4aNfHvatmymuggXQVkA==", + "requires": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.10.0", + "@stacks/network": "^6.11.3", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + } } }, "@stencil/core": { @@ -19914,9 +20171,9 @@ } }, "async-mutex": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", - "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", "requires": { "tslib": "^2.4.0" } @@ -23655,6 +23912,14 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -24408,9 +24673,9 @@ } }, "nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==" + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", + "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==" }, "nano-time": { "version": "1.0.0", diff --git a/package.json b/package.json index 6161631e1..3ec56702a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@phosphor-icons/react": "^2.0.10", "@react-spring/web": "^9.6.1", "@scure/btc-signer": "1.2.1", - "@secretkeylabs/xverse-core": "11.1.2", + "@secretkeylabs/xverse-core": "11.3.0", "@stacks/connect": "7.4.1", "@stacks/stacks-blockchain-api-types": "6.1.1", "@stacks/transactions": "6.9.0", diff --git a/src/app/components/confirmBtcTransaction/index.tsx b/src/app/components/confirmBtcTransaction/index.tsx index bf440755c..e47231d02 100644 --- a/src/app/components/confirmBtcTransaction/index.tsx +++ b/src/app/components/confirmBtcTransaction/index.tsx @@ -6,7 +6,7 @@ import ActionButton from '@components/button'; import LedgerConnectionView from '@components/ledger/connectLedgerView'; import useWalletSelector from '@hooks/useWalletSelector'; import TransportFactory from '@ledgerhq/hw-transport-webusb'; -import { btcTransaction, Transport } from '@secretkeylabs/xverse-core'; +import { btcTransaction, FungibleToken, Transport } from '@secretkeylabs/xverse-core'; import Callout from '@ui-library/callout'; import { StickyHorizontalSplitButtonContainer, StyledP } from '@ui-library/common.styled'; import Spinner from '@ui-library/spinner'; @@ -52,6 +52,9 @@ type Props = { isSubmitting: boolean; isBroadcast?: boolean; isError?: boolean; + token?: FungibleToken; + amountToSend?: string; + recipientAddress?: string; showAccountHeader?: boolean; hideBottomBar?: boolean; cancelText: string; @@ -76,6 +79,9 @@ function ConfirmBtcTransaction({ isSubmitting, isBroadcast, isError = false, + token, + amountToSend, + recipientAddress, cancelText, confirmText, onConfirm, @@ -167,6 +173,9 @@ function ConfirmBtcTransaction({ {!isBroadcast && } - } /> + } />
diff --git a/src/app/components/confirmBtcTransaction/itemRow/runeAmount.tsx b/src/app/components/confirmBtcTransaction/itemRow/runeAmount.tsx new file mode 100644 index 000000000..cf01aa076 --- /dev/null +++ b/src/app/components/confirmBtcTransaction/itemRow/runeAmount.tsx @@ -0,0 +1,93 @@ +import TokenImage from '@components/tokenImage'; +import { FungibleToken } from '@secretkeylabs/xverse-core'; +import Avatar from '@ui-library/avatar'; +import { StyledP } from '@ui-library/common.styled'; +import { getFtTicker } from '@utils/tokens'; +import { useTranslation } from 'react-i18next'; +import { NumericFormat } from 'react-number-format'; +import styled from 'styled-components'; + +type Props = { + amountSats: number; + token: FungibleToken; + amountToSend: string; +}; + +const Container = styled.div({ + width: '100%', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', +}); + +const AvatarContainer = styled.div` + margin-right: ${(props) => props.theme.space.xs}; +`; + +const ColumnContainer = styled.div({ + width: '100%', + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + gap: '24px', + whiteSpace: 'nowrap', + overflow: 'hidden', +}); + +const NumberTypeContainer = styled.div` + text-align: right; + overflow: hidden; +`; + +const EllipsisStyledP = styled(StyledP)` + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +export default function RuneAmount({ amountSats, token, amountToSend }: Props) { + const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); + + return ( + + + + } + /> + + +
+ + {t('AMOUNT')} + + + {t('BITCOIN_VALUE')} + +
+ + ( + {value} + )} + /> + + {`${amountSats} ${t('SATS')}`} + + +
+
+ ); +} diff --git a/src/app/components/confirmBtcTransaction/transactionSummary.tsx b/src/app/components/confirmBtcTransaction/transactionSummary.tsx index e81ff4f11..d49a5cc3a 100644 --- a/src/app/components/confirmBtcTransaction/transactionSummary.tsx +++ b/src/app/components/confirmBtcTransaction/transactionSummary.tsx @@ -4,7 +4,7 @@ import useWalletSelector from '@hooks/useWalletSelector'; import AssetModal from '@components/assetModal'; import TransferFeeView from '@components/transferFeeView'; import useBtcFeeRate from '@hooks/useBtcFeeRate'; -import { btcTransaction, getBtcFiatEquivalent } from '@secretkeylabs/xverse-core'; +import { btcTransaction, FungibleToken, getBtcFiatEquivalent } from '@secretkeylabs/xverse-core'; import SelectFeeRate from '@ui-components/selectFeeRate'; import Callout from '@ui-library/callout'; import { BLOG_LINK } from '@utils/constants'; @@ -38,10 +38,12 @@ const UnconfirmedInputCallout = styled(Callout)` type Props = { isPartialTransaction: boolean; - inputs: btcTransaction.EnhancedInput[]; outputs: btcTransaction.EnhancedOutput[]; feeOutput?: btcTransaction.TransactionFeeOutput; + token?: FungibleToken; + amountToSend?: string; + recipientAddress?: string; getFeeForFeeRate?: ( feeRate: number, useEffectiveFeeRate?: boolean, @@ -52,10 +54,13 @@ type Props = { }; function TransactionSummary({ + isPartialTransaction, inputs, outputs, feeOutput, - isPartialTransaction, + token, + amountToSend, + recipientAddress, isSubmitting, getFeeForFeeRate, onFeeRateSet, @@ -104,6 +109,9 @@ function TransactionSummary({ const showFeeSelector = !!(feeRate && getFeeForFeeRate && onFeeRateSet); + // TODO - TEMP SOLUTION - we should detect this via the txContext (input/outputs) for proper PSBT support (v2) + const isRuneTransaction = token && token.protocol === 'runes' && amountToSend && recipientAddress; + return ( <> {inscriptionToShow && ( @@ -125,31 +133,27 @@ function TransactionSummary({ anchorRedirect={`${BLOG_LINK}/rare-satoshis`} /> )} - {isUnConfirmedInput && ( )} - - - - - {hasOutputScript && } - + {!isRuneTransaction && hasOutputScript && } - {feeOutput && !showFeeSelector && ( ({ padding: `0 ${props.theme.space.m}`, })); +const StyledStyledP = styled(StyledP)` + display: flex; + align-items: center; +`; + +const StyledArrowRight = styled(ArrowRight)({ + marginRight: 4, +}); + type Props = { outputs: btcTransaction.EnhancedOutput[]; inputs: btcTransaction.EnhancedInput[]; isPartialTransaction: boolean; netAmount: number; onShowInscription: (inscription: btcTransaction.IOInscription) => void; + token?: FungibleToken; + amountToSend?: string; + recipientAddress?: string; }; // if isPartialTransaction, we use inputs instead of outputs @@ -49,6 +65,9 @@ function TransferSection({ isPartialTransaction, netAmount, onShowInscription, + token, + amountToSend, + recipientAddress, }: Props) { const { btcAddress, ordinalsAddress } = useWalletSelector(); const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); @@ -80,16 +99,27 @@ function TransferSection({ if (!hasData) return null; + const isRuneTransaction = token && amountToSend && recipientAddress; + return (
{t('YOU_WILL_TRANSFER')} + {isRuneTransaction && ( + + + {getTruncatedAddress(recipientAddress, 6)} + + )}
{showAmount && ( - + {isRuneTransaction && ( + + )} + {!isRuneTransaction && } { - let amountInCurrency; - if (currencyType === 'FT') { - amountInCurrency = new BigNumber(value).multipliedBy(fungibleToken?.tokenFiatRate!); - if (amountInCurrency.isLessThan(0.01)) { - amountInCurrency = '0.01'; - } - } else { - amountInCurrency = getFiatEquivalent( + setFiatAmount( + getFiatEquivalent( Number(value), currencyType, BigNumber(stxBtcRate), BigNumber(btcFiatRate), fungibleToken, - ); - } - setFiatAmount(amountInCurrency); + ), + ); }, [value]); function getFtTicker() { @@ -193,7 +186,12 @@ function RecipientComponent({ return ( - + ); }; diff --git a/src/app/components/sendForm/index.tsx b/src/app/components/sendForm/index.tsx index c10231387..eb196bc9d 100644 --- a/src/app/components/sendForm/index.tsx +++ b/src/app/components/sendForm/index.tsx @@ -255,14 +255,15 @@ function SendForm({ return; } - const amountInCurrency = getFiatEquivalent( - Number(amountToSend), - currencyType, - BigNumber(stxBtcRate), - BigNumber(btcFiatRate), - fungibleToken, + setFiatAmount( + getFiatEquivalent( + Number(amountToSend), + currencyType, + BigNumber(stxBtcRate), + BigNumber(btcFiatRate), + fungibleToken, + ), ); - setFiatAmount(amountInCurrency); }, [amountToSend]); function getTokenCurrency(): string { @@ -290,15 +291,15 @@ function SendForm({ } else { setAmount(newValue); } - - const amountInCurrency = getFiatEquivalent( - Number(newValue), - currencyType, - BigNumber(stxBtcRate), - BigNumber(btcFiatRate), - fungibleToken, + setFiatAmount( + getFiatEquivalent( + Number(amountToSend), + currencyType, + BigNumber(stxBtcRate), + BigNumber(btcFiatRate), + fungibleToken, + ), ); - setFiatAmount(amountInCurrency); }; const getTokenEquivalent = (tokenAmount: string): string => { @@ -465,7 +466,7 @@ function SendForm({ !hideTokenImage && ( diff --git a/src/app/components/tokenImage/index.tsx b/src/app/components/tokenImage/index.tsx index a114759e2..42bd21acb 100644 --- a/src/app/components/tokenImage/index.tsx +++ b/src/app/components/tokenImage/index.tsx @@ -1,89 +1,147 @@ import IconBitcoin from '@assets/img/dashboard/bitcoin_icon.svg'; import IconStacks from '@assets/img/dashboard/stx_icon.svg'; -import BarLoader from '@components/barLoader'; +import StacksIcon from '@assets/img/nftDashboard/stacks_icon.svg'; +import OrdinalIcon from '@assets/img/transactions/ordinal.svg'; +import RunesIcon from '@assets/img/transactions/runes.svg'; +import { StyledBarLoader } from '@components/tilesSkeletonLoader'; import { FungibleToken } from '@secretkeylabs/xverse-core'; -import { LoaderSize } from '@utils/constants'; +import { CurrencyTypes } from '@utils/constants'; import { getTicker } from '@utils/helper'; import { useCallback } from 'react'; -import stc from 'string-to-color'; import styled from 'styled-components'; export interface TokenImageProps { - token?: string; - loading?: boolean; + currency?: CurrencyTypes; fungibleToken?: FungibleToken; + loading?: boolean; size?: number; - loaderSize?: LoaderSize; round?: boolean; + showProtocolIcon?: boolean; } +const DEFAULT_SIZE = 40; + const TickerImage = styled.img<{ size?: number }>((props) => ({ - height: props.size ?? 44, - width: props.size ?? 44, + height: props.size ?? DEFAULT_SIZE, + width: props.size ?? DEFAULT_SIZE, borderRadius: '50%', })); const LoaderImageContainer = styled.div({ - flex: 0.5, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }); const TickerIconContainer = styled.div<{ size?: number; round?: boolean }>((props) => ({ display: 'flex', alignItems: 'center', justifyContent: 'center', - height: props.size ?? 44, - width: props.size ?? 44, + height: props.size ?? DEFAULT_SIZE, + width: props.size ?? DEFAULT_SIZE, borderRadius: '50%', - backgroundColor: props.color, + backgroundColor: props.theme.colors.white_400, })); const TickerIconText = styled.h1((props) => ({ - ...props.theme.body_bold_m, - color: props.theme.colors.white_0, + ...props.theme.typography.body_bold_m, + color: props.theme.colors.elevation0, textAlign: 'center', wordBreak: 'break-all', - fontSize: 13, + fontSize: 11, })); +const TickerProtocolContainer = styled.div({ + position: 'relative', + alignSelf: 'center', + display: 'inline-flex', +}); + +const ProtocolIcon = styled.div<{ isSquare?: boolean }>((props) => ({ + width: props.isSquare ? 18 : 22, + height: props.isSquare ? 18 : 22, + borderRadius: props.isSquare ? 0 : 22, + position: 'absolute', + right: props.isSquare ? -9 : -11, + bottom: -2, + backgroundColor: props.theme.colors.elevation1, + padding: 2, +})); + +const ProtocolImage = styled.img({ + height: '100%', + width: '100%', +}); + export default function TokenImage({ - token, - loading, + currency, fungibleToken, + loading, size, - loaderSize, round, + showProtocolIcon = true, }: TokenImageProps) { - const getCoinIcon = useCallback(() => { - if (token === 'STX') { + const ftProtocol = fungibleToken?.protocol; + + const getCurrencyIcon = useCallback(() => { + if (currency === 'STX') { return IconStacks; } - if (token === 'BTC') { + if (currency === 'BTC') { return IconBitcoin; } - }, [token]); - - if (fungibleToken) { - if (!loading) { - if (fungibleToken?.image) { - return ; - } - let ticker = fungibleToken?.ticker; - if (!ticker && fungibleToken?.name) { - ticker = getTicker(fungibleToken?.name); - } - const background = stc(ticker); - ticker = ticker && ticker.substring(0, 4); - return ( - - {ticker} - - ); + }, [currency]); + + const getProtocolIcon = () => { + if (!ftProtocol) { + return null; + } + switch (ftProtocol) { + case 'stacks': + return ; + case 'brc-20': + return ; + case 'runes': + return ; + default: + return null; + } + }; + + const renderIcon = () => { + if (!fungibleToken) { + return ; + } + if (fungibleToken?.image) { + return ; } + const ticker = fungibleToken?.name + ? getTicker(fungibleToken.name) + : fungibleToken?.ticker || fungibleToken?.assetName || ''; + + return ( + + {ticker.substring(0, 4)} + + ); + }; + + if (loading) { return ( - - - + + + + + ); } - return ; + + return ( + + {renderIcon()} + {ftProtocol && showProtocolIcon && ( + {getProtocolIcon()} + )} + + ); } diff --git a/src/app/components/tokenTile/index.tsx b/src/app/components/tokenTile/index.tsx index 430827aa9..323f85ab6 100644 --- a/src/app/components/tokenTile/index.tsx +++ b/src/app/components/tokenTile/index.tsx @@ -1,15 +1,14 @@ import { BetterBarLoader } from '@components/barLoader'; import { StyledFiatAmountText } from '@components/fiatAmountText'; +import TokenImage from '@components/tokenImage'; import type { FungibleToken } from '@secretkeylabs/xverse-core'; import { microstacksToStx, satsToBtc } from '@secretkeylabs/xverse-core'; import { StoreState } from '@stores/index'; import { CurrencyTypes } from '@utils/constants'; -import { getTicker } from '@utils/helper'; import { getFtBalance, getFtTicker } from '@utils/tokens'; import BigNumber from 'bignumber.js'; import { NumericFormat } from 'react-number-format'; import { useSelector } from 'react-redux'; -import stc from 'string-to-color'; import styled from 'styled-components'; interface TileProps { @@ -17,9 +16,6 @@ interface TileProps { inModel: boolean; } -interface TickerProps { - enlargeTicker?: boolean; -} const TileContainer = styled.button((props) => ({ display: 'flex', flexDirection: 'row', @@ -33,51 +29,25 @@ const TileContainer = styled.button((props) => ({ marginBottom: props.inModel ? props.theme.spacing(0) : props.theme.spacing(6), })); -const TickerImage = styled.img((props) => ({ - marginRight: props.theme.spacing(3), - alignSelf: 'center', - transform: 'all', - height: props.enlargeTicker ? 40 : 32, - width: props.enlargeTicker ? 40 : 32, - borderRadius: '50%', -})); - -const TickerIconContainer = styled.div((props) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - height: props.enlargeTicker ? 40 : 32, - width: props.enlargeTicker ? 40 : 32, - marginRight: props.theme.spacing(3), - borderRadius: '50%', - backgroundColor: props.color, -})); - -const TickerIconText = styled.h1((props) => ({ - ...props.theme.typography.body_bold_m, - color: props.theme.colors.white_0, - textAlign: 'center', - wordBreak: 'break-all', - fontSize: 10, -})); - const RowContainer = styled.div({ flex: '1 0 auto', display: 'flex', }); const TextContainer = styled.div((props) => ({ - marginLeft: props.theme.spacing(6), + marginLeft: props.theme.space.m, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', })); -const AmountContainer = styled.div({ +const AmountContainer = styled.div((props) => ({ display: 'flex', flexDirection: 'column', + marginLeft: props.theme.space.xxs, + overflow: 'hidden', alignItems: 'flex-end', -}); +})); const LoaderMainContainer = styled.div({ flex: '1 1 auto', @@ -86,19 +56,12 @@ const LoaderMainContainer = styled.div({ alignItems: 'flex-end', }); -const LoaderImageContainer = styled.div((props) => ({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - marginRight: props.theme.spacing(3), -})); - -const CoinTickerText = styled.h1((props) => ({ +const CoinTickerText = styled.p((props) => ({ ...props.theme.typography.body_bold_m, color: props.theme.colors.white_0, })); -const SubText = styled.h1((props) => ({ +const SubText = styled.p((props) => ({ ...props.theme.headline_category_s, color: props.theme.colors.white_400, fontSize: 12, @@ -108,10 +71,12 @@ const SubText = styled.h1((props) => ({ textOverflow: 'ellipsis', })); -const CoinBalanceText = styled.h1((props) => ({ +const CoinBalanceText = styled.p((props) => ({ ...props.theme.typography.body_medium_m, color: props.theme.colors.white_0, - textAlign: 'end', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '100%', })); const TokenTitleContainer = styled.div({ @@ -140,22 +105,16 @@ function TokenLoader() { interface Props { title: string; - icon?: string; underlayColor: string; loading: boolean; margin?: number; - currency?: CurrencyTypes; - onPress: (token: { - coin: CurrencyTypes; - ft: string | undefined; - brc20Ft?: string | undefined; - }) => void; + currency: CurrencyTypes; + onPress: (coin: CurrencyTypes, ftKey: string | undefined) => void; fungibleToken?: FungibleToken; enlargeTicker?: boolean; } function TokenTile({ - icon, title, underlayColor, loading, @@ -182,8 +141,6 @@ function TokenTile({ return satsToBtc(new BigNumber(btcBalance)).toString(); case 'FT': return fungibleToken ? getFtBalance(fungibleToken) : ''; - case 'brc20': - return fungibleToken ? getFtBalance(fungibleToken) : ''; default: } } @@ -197,7 +154,6 @@ function TokenTile({ case 'BTC': return satsToBtc(new BigNumber(btcBalance)).multipliedBy(btcFiatRate); case 'FT': - case 'brc20': return fungibleToken?.tokenFiatRate ? new BigNumber(getFtBalance(fungibleToken)).multipliedBy(fungibleToken.tokenFiatRate) : undefined; @@ -206,44 +162,7 @@ function TokenTile({ } } - function renderFTIcon() { - if (!loading) { - if (fungibleToken?.image) { - return ; - } - // render ticker icon - let ticker = fungibleToken?.ticker; - if (!ticker && fungibleToken?.name) { - ticker = getTicker(fungibleToken?.name); - } - const background = stc(ticker); - ticker = ticker && ticker.substring(0, 4); - return ( - - {ticker} - - ); - } - return ( - - - - ); - } - - function renderIcon() { - if (currency === 'STX' || currency === 'BTC') - return ; - return renderFTIcon(); - } - - const handleTokenPressed = () => { - onPress({ - coin: currency as CurrencyTypes, - ft: fungibleToken && fungibleToken.principal, - brc20Ft: currency === 'brc20' ? fungibleToken?.principal : undefined, - }); - }; + const handleTokenPressed = () => onPress(currency, fungibleToken?.principal); return ( - {renderIcon()} + {getTickerTitle()} diff --git a/src/app/components/transactions/btcTransaction.tsx b/src/app/components/transactions/btcOrBrc20Transaction.tsx similarity index 92% rename from src/app/components/transactions/btcTransaction.tsx rename to src/app/components/transactions/btcOrBrc20Transaction.tsx index ffec7015c..66c8915c9 100644 --- a/src/app/components/transactions/btcTransaction.tsx +++ b/src/app/components/transactions/btcOrBrc20Transaction.tsx @@ -77,12 +77,13 @@ interface TransactionHistoryItemProps { transaction: BtcTransactionData | Brc20HistoryTransactionData; wallet: RBFProps; } -export default function BtcTransactionHistoryItem({ +export default function BtcOrBrc20TransactionHistoryItem({ transaction, wallet, }: TransactionHistoryItemProps) { const { network, hasActivatedRBFKey } = useWalletSelector(); - const isBtc = isBtcTransaction(transaction) ? 'BTC' : 'brc20'; + const currency = isBtcTransaction(transaction) ? 'BTC' : 'FT'; + const protocol = currency === 'FT' ? 'brc-20' : undefined; const theme = useTheme(); const { t } = useTranslation('translation', { keyPrefix: 'COIN_DASHBOARD_SCREEN' }); @@ -94,10 +95,9 @@ export default function BtcTransactionHistoryItem({ hasActivatedRBFKey && isBtcTransaction(transaction) && rbf.isTransactionRbfEnabled(transaction, wallet); - return ( - +
@@ -105,7 +105,7 @@ export default function BtcTransactionHistoryItem({
- + {showAccelerateButton && (
- + {showAccelerateButton && ( ({ - ...props.theme.body_medium_m, + ...props.theme.typography.body_medium_m, color: props.theme.colors.white_0, })); export default function TransactionAmount(props: TransactionAmountProps): JSX.Element | null { - const { transaction, coin } = props; - const { coinsList } = useWalletSelector(); - if (coin === 'STX' || coin === 'FT') { + const { transaction, currency, protocol } = props; + const { visible: sip10CoinsList } = useVisibleSip10FungibleTokens(); + if (currency === 'STX' || (currency === 'FT' && protocol === 'stacks')) { if (transaction.txType === 'token_transfer') { const prefix = transaction.incoming ? '' : '-'; return ( @@ -35,13 +37,16 @@ export default function TransactionAmount(props: TransactionAmountProps): JSX.El displayType="text" thousandSeparator prefix={prefix} - renderText={(value: string) => {`${value} ${coin}`}} + allowNegative={false} + renderText={(value: string) => ( + {`${value} ${currency}`} + )} /> ); } if (transaction.txType === 'contract_call') { if (transaction.tokenType === 'fungible') { - const token = coinsList?.find( + const token = sip10CoinsList.find( (cn) => cn.principal === transaction.contractCall?.contract_id, ); const prefix = transaction.incoming ? '' : '-'; @@ -51,6 +56,7 @@ export default function TransactionAmount(props: TransactionAmountProps): JSX.El displayType="text" thousandSeparator prefix={prefix} + allowNegative={false} renderText={(value: string) => ( {`${value} ${getFtTicker( token as FungibleToken, @@ -60,7 +66,7 @@ export default function TransactionAmount(props: TransactionAmountProps): JSX.El ); } } - } else if (coin === 'BTC') { + } else if (currency === 'BTC') { const btcTransaction = transaction as BtcTransactionData; const prefix = btcTransaction.incoming ? '' : '-'; if (btcTransaction.isOrdinal && btcTransaction.txStatus === 'pending') { @@ -72,14 +78,13 @@ export default function TransactionAmount(props: TransactionAmountProps): JSX.El value={satsToBtc(BigNumber(btcTransaction.amount)).toString()} displayType="text" thousandSeparator - prefix="" - renderText={(value: string) => ( - {`${prefix}${value} BTC`} - )} + prefix={prefix} + allowNegative={false} + renderText={(value: string) => {`${value} BTC`}} /> ); } - } else if (coin === 'brc20') { + } else if (currency === 'FT' && protocol === 'brc-20') { const brc20Transaction = transaction as Brc20HistoryTransactionData; const prefix = brc20Transaction.incoming ? '' : '-'; if (!new BigNumber(brc20Transaction.amount).isEqualTo(0)) { @@ -88,9 +93,10 @@ export default function TransactionAmount(props: TransactionAmountProps): JSX.El value={BigNumber(brc20Transaction.amount).toString()} displayType="text" thousandSeparator - prefix="" + prefix={prefix} + allowNegative={false} renderText={(value: string) => ( - {`${prefix}${value} ${brc20Transaction.ticker.toUpperCase()}`} + {`${value} ${brc20Transaction.ticker.toUpperCase()}`} )} /> ); diff --git a/src/app/components/transactions/transactionStatusIcon.tsx b/src/app/components/transactions/transactionStatusIcon.tsx index b32eaa863..0fb68e323 100644 --- a/src/app/components/transactions/transactionStatusIcon.tsx +++ b/src/app/components/transactions/transactionStatusIcon.tsx @@ -7,6 +7,7 @@ import SendIcon from '@assets/img/transactions/sent.svg'; import { Brc20HistoryTransactionData, BtcTransactionData, + FungibleTokenProtocol, StxTransactionData, } from '@secretkeylabs/xverse-core'; import { CurrencyTypes } from '@utils/constants'; @@ -14,11 +15,12 @@ import { CurrencyTypes } from '@utils/constants'; interface TransactionStatusIconPros { transaction: StxTransactionData | BtcTransactionData | Brc20HistoryTransactionData; currency: CurrencyTypes; + protocol?: FungibleTokenProtocol; } function TransactionStatusIcon(props: TransactionStatusIconPros) { - const { currency, transaction } = props; - if (currency === 'STX' || currency === 'FT') { + const { currency, transaction, protocol } = props; + if (currency === 'STX' || (currency === 'FT' && protocol === 'stacks')) { const tx = transaction as StxTransactionData; if (tx.txStatus === 'abort_by_response' || tx.txStatus === 'abort_by_post_condition') { return pending; @@ -50,7 +52,7 @@ function TransactionStatusIcon(props: TransactionStatusIconPros) { } return sent; } - if (currency === 'brc20') { + if (currency === 'FT' && protocol === 'brc-20') { const tx = transaction as Brc20HistoryTransactionData; if (tx.txStatus === 'pending') { return pending; diff --git a/src/app/components/transactions/transactionTitle.tsx b/src/app/components/transactions/transactionTitle.tsx index f19fded4c..04abc0737 100644 --- a/src/app/components/transactions/transactionTitle.tsx +++ b/src/app/components/transactions/transactionTitle.tsx @@ -1,4 +1,4 @@ -import useWalletSelector from '@hooks/useWalletSelector'; +import { useVisibleSip10FungibleTokens } from '@hooks/queries/stx/useGetSip10FungibleTokens'; import { Brc20HistoryTransactionData, BtcTransactionData, @@ -14,7 +14,7 @@ interface TransactionTitleProps { } const TransactionTitleText = styled.p((props) => ({ - ...props.theme.body_bold_m, + ...props.theme.typography.body_bold_m, color: props.theme.colors.white_0, textAlign: 'left', })); @@ -22,7 +22,7 @@ const TransactionTitleText = styled.p((props) => ({ export default function TransactionTitle(props: TransactionTitleProps) { const { transaction } = props; const { t } = useTranslation('translation', { keyPrefix: 'COIN_DASHBOARD_SCREEN' }); - const { coins } = useWalletSelector(); + const { visible: sip10CoinsList } = useVisibleSip10FungibleTokens(); const isPending = transaction.txStatus === 'pending'; const getTokenTransferTitle = (tx: TransactionData): string => { @@ -61,8 +61,8 @@ export default function TransactionTitle(props: TransactionTitleProps) { }; const getFtName = (tx: TransactionData): string => { - const coinDisplayName = coins?.find( - (coin) => coin.contract === tx.contractCall?.contract_id, + const coinDisplayName = sip10CoinsList.find( + (coin) => coin.principal === tx.contractCall?.contract_id, )?.name; return coinDisplayName ?? ''; diff --git a/src/app/components/transactions/txTransfers.tsx b/src/app/components/transactions/txTransfers.tsx index dab7adfcf..7480146c6 100644 --- a/src/app/components/transactions/txTransfers.tsx +++ b/src/app/components/transactions/txTransfers.tsx @@ -1,5 +1,6 @@ import ReceiveIcon from '@assets/img/transactions/received.svg'; import SendIcon from '@assets/img/transactions/sent.svg'; +import { useVisibleSip10FungibleTokens } from '@hooks/queries/stx/useGetSip10FungibleTokens'; import useWalletSelector from '@hooks/useWalletSelector'; import { microstacksToStx } from '@secretkeylabs/xverse-core'; import { AddressTransactionWithTransfers } from '@stacks/stacks-blockchain-api-types'; @@ -31,22 +32,22 @@ const TransactionRow = styled.div((props) => ({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', - ...props.theme.body_bold_m, + ...props.theme.typography.body_bold_m, })); const TransactionTitleText = styled.p((props) => ({ - ...props.theme.body_bold_m, + ...props.theme.typography.body_bold_m, color: props.theme.colors.white_0, })); const RecipientAddress = styled.p((props) => ({ - ...props.theme.body_xs, + ...props.theme.typography.body_s, color: props.theme.colors.white_400, textAlign: 'left', })); const TransactionValue = styled.p((props) => ({ - ...props.theme.body_medium_m, + ...props.theme.typography.body_medium_m, color: props.theme.colors.white_0, })); @@ -58,7 +59,8 @@ interface TxTransfersProps { export default function TxTransfers(props: TxTransfersProps) { const { transaction, coin, txFilter } = props; - const { selectedAccount, coinsList } = useWalletSelector(); + const { selectedAccount } = useWalletSelector(); + const { visible: sip10CoinsList } = useVisibleSip10FungibleTokens(); const { t } = useTranslation('translation', { keyPrefix: 'COIN_DASHBOARD_SCREEN' }); function formatAddress(addr: string): string { @@ -80,7 +82,7 @@ export default function TxTransfers(props: TxTransfersProps) { function renderTransaction(transactionList) { return transactionList.map((transfer) => { const isFT = coin === 'FT'; - const ft = coinsList?.find((ftCoin) => ftCoin.principal === txFilter!.split('::')[0]); + const ft = sip10CoinsList.find((ftCoin) => ftCoin.principal === txFilter!.split('::')[0]); const isSentTransaction = selectedAccount?.stxAddress !== transfer.recipient; if (isFT && transfer.asset_identifier !== txFilter) { return null; @@ -100,6 +102,7 @@ export default function TxTransfers(props: TxTransfersProps) { } displayType="text" thousandSeparator + allowNegative={false} prefix={isSentTransaction ? '-' : ''} renderText={(value: string) => ( diff --git a/src/app/components/transferAmountView/index.tsx b/src/app/components/transferAmountView/index.tsx deleted file mode 100644 index a58047e30..000000000 --- a/src/app/components/transferAmountView/index.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import useWalletSelector from '@hooks/useWalletSelector'; -import { FungibleToken, getFiatEquivalent } from '@secretkeylabs/xverse-core'; -import { getTicker } from '@utils/helper'; -import BigNumber from 'bignumber.js'; -import { useEffect, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { NumericFormat } from 'react-number-format'; -import styled from 'styled-components'; - -const SendAmountContainer = styled.div({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', -}); - -const TitleText = styled.h1((props) => ({ - ...props.theme.headline_category_s, - color: props.theme.colors.white_400, - textTransform: 'uppercase', -})); - -const AmountText = styled.h1((props) => ({ - ...props.theme.headline_category_m, - textTransform: 'uppercase', - fontSize: 28, -})); - -const FiatAmountText = styled.h1((props) => ({ - ...props.theme.body_m, - color: props.theme.colors.white_400, -})); - -interface Props { - amount: BigNumber; - currency: string; - fungibleToken?: FungibleToken; -} - -function TransferAmountView({ amount, currency, fungibleToken }: Props) { - const { t } = useTranslation('translation', { keyPrefix: 'CONFIRM_TRANSACTION' }); - const [fiatAmount, setFiatAmount] = useState('0'); - const { stxBtcRate, btcFiatRate, fiatCurrency } = useWalletSelector(); - - function getFtTicker() { - if (fungibleToken?.ticker) { - return fungibleToken?.ticker.toUpperCase(); - } - if (fungibleToken?.name) { - return getTicker(fungibleToken.name).toUpperCase(); - } - return ''; - } - - useEffect(() => { - let amountInCurrency; - if (currency === 'FT') { - amountInCurrency = new BigNumber(amount).multipliedBy(fungibleToken?.tokenFiatRate!); - if (amountInCurrency.isLessThan(0.01)) { - amountInCurrency = '0.01'; - } - } else { - amountInCurrency = getFiatEquivalent( - Number(amount), - currency, - new BigNumber(stxBtcRate), - new BigNumber(btcFiatRate), - fungibleToken, - ); - } - setFiatAmount(amountInCurrency); - }, [amount]); - - return ( - - {t('INDICATION')} - {value}} - /> - {`~ $ ${fiatAmount} ${fiatCurrency}`} - - ); -} - -export default TransferAmountView; diff --git a/src/app/components/transferFeeView/index.tsx b/src/app/components/transferFeeView/index.tsx index cd3e65dc5..541c1a705 100644 --- a/src/app/components/transferFeeView/index.tsx +++ b/src/app/components/transferFeeView/index.tsx @@ -62,12 +62,7 @@ function TransferFeeView({ const { btcFiatRate, stxBtcRate, fiatCurrency } = useSelector( (state: StoreState) => state.walletState, ); - const fiatRate = getFiatEquivalent( - Number(fee), - currency, - BigNumber(stxBtcRate), - BigNumber(btcFiatRate), - ); + const getFiatAmountString = (fiatAmount: BigNumber) => { if (!fiatAmount) { return ''; @@ -126,7 +121,14 @@ function TransferFeeView({ {getFiatAmountString( currency === 'sats' ? getBtcFiatEquivalent(new BigNumber(fee), BigNumber(btcFiatRate)) - : new BigNumber(fiatRate!), + : new BigNumber( + getFiatEquivalent( + Number(fee), + 'STX', + BigNumber(stxBtcRate), + BigNumber(btcFiatRate), + )!, + ), )} diff --git a/src/app/components/updatedBottomModal/index.tsx b/src/app/components/updatedBottomModal/index.tsx index 5d7c0b6ba..4b464b954 100644 --- a/src/app/components/updatedBottomModal/index.tsx +++ b/src/app/components/updatedBottomModal/index.tsx @@ -3,7 +3,7 @@ import Modal from 'react-modal'; import styled, { useTheme } from 'styled-components'; const BottomModalHeaderText = styled.h1((props) => ({ - ...props.theme.body_bold_l, + ...props.theme.typography.body_bold_l, flex: 1, })); @@ -64,8 +64,8 @@ function UpdatedBottomModal({ maxWidth: 360, maxHeight: '90%', border: 'transparent', - background: theme.colors.background.elevation6_600, - backdropFilter: 'blur(10px)', + background: theme.colors.elevation6_600, + backdropFilter: 'blur(24px)', margin: 0, padding: 0, borderTopLeftRadius: isGalleryOpen ? 12 : 16, diff --git a/src/app/hooks/queries/ordinals/useGetBrc20FungibleTokens.ts b/src/app/hooks/queries/ordinals/useGetBrc20FungibleTokens.ts new file mode 100644 index 000000000..5fa318baa --- /dev/null +++ b/src/app/hooks/queries/ordinals/useGetBrc20FungibleTokens.ts @@ -0,0 +1,76 @@ +import useWalletSelector from '@hooks/useWalletSelector'; +import { + Brc20Token, + FungibleToken, + SettingsNetwork, + getBrc20Tokens, + getOrdinalsFtBalance, +} from '@secretkeylabs/xverse-core'; +import { useQuery } from '@tanstack/react-query'; + +export const brc20TokenToFungibleToken = (coin: Brc20Token): FungibleToken => ({ + name: coin.name, + principal: coin.ticker ?? coin.name, + balance: '0', + total_sent: '', + total_received: '', + assetName: coin.name ?? coin.ticker, + visible: true, + ticker: coin.ticker, + protocol: 'brc-20', + supported: coin.supported, +}); + +export const fetchBrc20FungibleTokens = + (ordinalsAddress: string, fiatCurrency: string, network: SettingsNetwork) => async () => { + // get brc20 balances for the ordinalsAddress + const ordinalsFtBalance: FungibleToken[] = ( + await getOrdinalsFtBalance(network.type, ordinalsAddress) + ).map((ft) => ({ ...ft, ticker: ft.ticker?.toUpperCase() })); + + // get extra metadata (tokenFiatRate) including supported tokens without balance + const tickers = ordinalsFtBalance.filter((ft) => ft.ticker).map((ft) => ft.ticker!) ?? []; + const brc20Tokens = (await getBrc20Tokens(network.type, tickers, fiatCurrency)) || []; + + // combine the two, into a unique list of fungible tokens + return ordinalsFtBalance + .map((ft) => { + const found = brc20Tokens.find((coin) => coin.ticker === ft.ticker); + if (!found) { + return ft; + } + return { + ...ft, + tokenFiatRate: Number(found.tokenFiatRate), + name: found.name, + supported: found.supported, + }; + }) + .concat( + brc20Tokens + .filter((coin) => !ordinalsFtBalance.some((ft) => ft.ticker === coin.ticker)) + .map((coin) => brc20TokenToFungibleToken(coin)), + ); + }; + +export const useGetBrc20FungibleTokens = () => { + const { ordinalsAddress, fiatCurrency, network } = useWalletSelector(); + const queryFn = fetchBrc20FungibleTokens(ordinalsAddress, fiatCurrency, network); + + return useQuery({ + queryKey: ['brc20-fungible-tokens', ordinalsAddress, network.type], + queryFn, + enabled: Boolean(network && ordinalsAddress), + }); +}; + +export const useVisibleBrc20FungibleTokens = (): ReturnType & { + visible: FungibleToken[]; +} => { + const { brc20ManageTokens } = useWalletSelector(); + const brc20Query = useGetBrc20FungibleTokens(); + return { + ...brc20Query, + visible: (brc20Query.data ?? []).filter((ft) => brc20ManageTokens[ft.principal] !== false), + }; +}; diff --git a/src/app/hooks/queries/runes/useGetRuneFungibleTokens.ts b/src/app/hooks/queries/runes/useGetRuneFungibleTokens.ts new file mode 100644 index 000000000..0efbbe6e4 --- /dev/null +++ b/src/app/hooks/queries/runes/useGetRuneFungibleTokens.ts @@ -0,0 +1,31 @@ +import useHasFeature from '@hooks/useHasFeature'; +import useRunesApi from '@hooks/useRunesApi'; +import useWalletSelector from '@hooks/useWalletSelector'; +import { FungibleToken } from '@secretkeylabs/xverse-core'; +import { useQuery } from '@tanstack/react-query'; + +export const useGetRuneFungibleTokens = () => { + const { ordinalsAddress, network } = useWalletSelector(); + const showRunes = useHasFeature('RUNES_SUPPORT'); + const RunesApi = useRunesApi(); + return useQuery({ + queryKey: ['get-rune-fungible-tokens', network.type, ordinalsAddress], + // TODO: remove showRunes once available in mainnet as well + enabled: Boolean(network && ordinalsAddress && showRunes), + queryFn: async () => RunesApi.getRuneFungibleTokens(ordinalsAddress), + }); +}; + +/* + * This hook is used to get the list of runes which the user has not hidden + */ +export const useVisibleRuneFungibleTokens = (): ReturnType & { + visible: FungibleToken[]; +} => { + const { runesManageTokens } = useWalletSelector(); + const runesQuery = useGetRuneFungibleTokens(); + return { + ...runesQuery, + visible: (runesQuery.data ?? []).filter((rune) => runesManageTokens[rune.principal] !== false), + }; +}; diff --git a/src/app/hooks/queries/stx/useGetSip10FungibleTokens.ts b/src/app/hooks/queries/stx/useGetSip10FungibleTokens.ts new file mode 100644 index 000000000..238d9fdfe --- /dev/null +++ b/src/app/hooks/queries/stx/useGetSip10FungibleTokens.ts @@ -0,0 +1,85 @@ +import useNetworkSelector from '@hooks/useNetwork'; +import useWalletSelector from '@hooks/useWalletSelector'; +import { + FungibleToken, + SettingsNetwork, + StacksNetwork, + // getCoinMetaData, + getCoinsInfo, + getFtData, +} from '@secretkeylabs/xverse-core'; +import { useQuery } from '@tanstack/react-query'; + +export const fetchSip10FungibleTokens = + ( + stxAddress: string, + fiatCurrency: string, + network: SettingsNetwork, + currentNetworkInstance: StacksNetwork, + ) => + async () => { + // get sip10 metadata and balances for the stxAddress + const sip10Balances = await getFtData(stxAddress, currentNetworkInstance); + + // getting contract ids of all fts + const contractids: string[] = sip10Balances.map((ft) => ft.principal); + const sip10ContractInfos = (await getCoinsInfo(network.type, contractids, fiatCurrency)) || []; + + // combine + return sip10Balances + .map((ft) => { + const found = (sip10ContractInfos || []).find((coin) => coin.contract === ft.principal); + if (!found) { + return ft; + } + return { + ...ft, + ...found, + visible: true, + name: found.name || ft.principal.split('.')[1], + }; + }) + .concat( + sip10ContractInfos + .filter((coin) => !sip10Balances.some((ft) => ft.principal === coin.contract)) + .map((coin) => ({ + ...coin, + principal: coin.contract, + assetName: coin.name || coin.contract.split('.')[1], + protocol: 'stacks', + balance: '0', + visible: true, + total_sent: '', + total_received: '', + })), + ); + }; + +export const useGetSip10FungibleTokens = () => { + const { stxAddress, fiatCurrency, network } = useWalletSelector(); + const currentNetworkInstance = useNetworkSelector(); + + const queryFn = fetchSip10FungibleTokens( + stxAddress, + fiatCurrency, + network, + currentNetworkInstance, + ); + + return useQuery({ + queryKey: ['sip10-fungible-tokens', network.type, stxAddress], + queryFn, + enabled: Boolean(network && stxAddress), + }); +}; + +export const useVisibleSip10FungibleTokens = (): ReturnType & { + visible: FungibleToken[]; +} => { + const { sip10ManageTokens } = useWalletSelector(); + const sip10Query = useGetSip10FungibleTokens(); + return { + ...sip10Query, + visible: (sip10Query.data ?? []).filter((ft) => sip10ManageTokens[ft.principal] !== false), + }; +}; diff --git a/src/app/hooks/queries/useAccountBalance.ts b/src/app/hooks/queries/useAccountBalance.ts index 2e86c7b26..1c5f955a1 100644 --- a/src/app/hooks/queries/useAccountBalance.ts +++ b/src/app/hooks/queries/useAccountBalance.ts @@ -2,15 +2,12 @@ import useBtcClient from '@hooks/useBtcClient'; import useNetworkSelector from '@hooks/useNetwork'; import useWalletSelector from '@hooks/useWalletSelector'; import { - API_TIMEOUT_MILLI, Account, + API_TIMEOUT_MILLI, BtcAddressData, FungibleToken, - TokensResponse, - getBrc20Tokens, - getCoinsInfo, getNetworkURL, - getOrdinalsFtBalance, + TokensResponse, } from '@secretkeylabs/xverse-core'; import { setAccountBalanceAction } from '@stores/wallet/actions/actionCreators'; import { calculateTotalBalance } from '@utils/helper'; @@ -18,73 +15,26 @@ import axios from 'axios'; import BigNumber from 'bignumber.js'; import { useEffect, useRef, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { brc20TokenToFungibleToken } from './useBtcCoinsBalance'; +import { fetchBrc20FungibleTokens } from './ordinals/useGetBrc20FungibleTokens'; +import { fetchSip10FungibleTokens } from './stx/useGetSip10FungibleTokens'; const useAccountBalance = () => { const btcClient = useBtcClient(); const stacksNetwork = useNetworkSelector(); - const { btcFiatRate, stxBtcRate, fiatCurrency, network, coinsList, brcCoinsList, hideStx } = - useWalletSelector(); + const { + btcFiatRate, + stxBtcRate, + fiatCurrency, + network, + hideStx, + brc20ManageTokens, + sip10ManageTokens, + } = useWalletSelector(); const dispatch = useDispatch(); const queue = useRef([]); const [isProcessingQueue, setIsProcessingQueue] = useState(false); const [queueLength, setQueueLength] = useState(0); - // TODO: reuse it for both useAccountBalance and useBtcCoinBalance hooks - const fetchBrcCoinsBalances = async (ordinalsAddress: string) => { - try { - const ordinalsFtBalance = await getOrdinalsFtBalance(network.type, ordinalsAddress); - const tickers = ordinalsFtBalance?.map((o) => o.ticker!) ?? []; - const brc20Tokens = await getBrc20Tokens( - network.type, - // workaround for brc20 tokens not being returned - tickers.length ? tickers : ['ORDI'], - fiatCurrency, - ); - - const brcCoinsListMap = new Map(brcCoinsList?.map((token) => [token.ticker, token])); - - const mergedList: FungibleToken[] = ordinalsFtBalance.map((newToken) => { - const existingToken = brcCoinsListMap.get(newToken.ticker); - - const reconstitutedFt = { - ...existingToken, - ...newToken, - // The `visible` property from `xverse-core` defaults to true. - // We override `visible` to ensure that the existing state is preserved. - ...(existingToken ? { visible: existingToken.visible } : {}), - }; - - return reconstitutedFt; - }); - - brc20Tokens?.forEach((b) => { - const existingToken = brcCoinsListMap.get(b.ticker); - const pendingToken = mergedList?.find((m) => m.ticker === b.ticker); - const tokenFiatRate = Number(b?.tokenFiatRate); - - // No duplicates - if (pendingToken) { - pendingToken.tokenFiatRate = tokenFiatRate; - return; - } - - if (existingToken) { - mergedList.push({ - ...existingToken, - tokenFiatRate, - }); - } else { - mergedList.push(brc20TokenToFungibleToken(b)); - } - }); - - return mergedList; - } catch (e: any) { - return Promise.reject(e); - } - }; - const fetchBalances = async (account: Account | null) => { if (!account) { return; @@ -92,8 +42,8 @@ const useAccountBalance = () => { let btcBalance = '0'; let stxBalance = '0'; - let ftCoinList: FungibleToken[] | null = null; - let finalBrcCoinList: FungibleToken[] | null = null; + let finalSipCoinsList: FungibleToken[] = []; + let finalBrcCoinsList: FungibleToken[] = []; if (account.btcAddress) { const btcData: BtcAddressData = await btcClient.getBalance(account.btcAddress); @@ -101,7 +51,14 @@ const useAccountBalance = () => { } if (account.ordinalsAddress) { - finalBrcCoinList = await fetchBrcCoinsBalances(account.ordinalsAddress); + const fetchBrc20Balances = fetchBrc20FungibleTokens( + account.ordinalsAddress, + fiatCurrency, + network, + ); + finalBrcCoinsList = (await fetchBrc20Balances()).filter( + (ft) => brc20ManageTokens[ft.principal] !== false, + ); } if (account.stxAddress) { @@ -117,69 +74,22 @@ const useAccountBalance = () => { const lockedBalance = new BigNumber(response.data.stx.locked); stxBalance = availableBalance.plus(lockedBalance).toString(); - const fungibleTokenList: FungibleToken[] = []; - Object.entries(response.data.fungible_tokens).forEach(([key, value]: [string, any]) => { - const fungibleToken: FungibleToken = value; - const index = key.indexOf('::'); - fungibleToken.assetName = key.substring(index + 2); - fungibleToken.principal = key.substring(0, index); - fungibleToken.protocol = 'stacks'; - fungibleTokenList.push(fungibleToken); - }); - - const visibleCoins: FungibleToken[] | null = coinsList; - if (visibleCoins) { - visibleCoins.forEach((visibleCoin) => { - const coinToBeUpdated = fungibleTokenList.find( - (ft) => ft.principal === visibleCoin.principal, - ); - if (coinToBeUpdated) coinToBeUpdated.visible = visibleCoin.visible; - else if (visibleCoin.visible) { - visibleCoin.balance = '0'; - fungibleTokenList.push(visibleCoin); - } - }); - } else { - fungibleTokenList.forEach((ft) => { - ft.visible = true; - }); - } - - const contractids: string[] = fungibleTokenList.map((ft) => ft.principal); - const coinsResponse = await getCoinsInfo(network.type, contractids, fiatCurrency); - - if (coinsResponse) { - coinsResponse.forEach((coin) => { - if (!coin.name) { - const coinName = coin.contract.split('.')[1]; - coin.name = coinName; - } - }); - - // update attributes of fungible token list - fungibleTokenList.forEach((ft) => { - coinsResponse.forEach((coin) => { - if (ft.principal === coin.contract) { - ft.ticker = coin.ticker; - ft.decimals = coin.decimals; - ft.supported = coin.supported; - ft.image = coin.image; - ft.name = coin.name; - ft.tokenFiatRate = coin.tokenFiatRate; - coin.visible = ft.visible; - } - }); - }); - - ftCoinList = fungibleTokenList; - } + const fetchSip10Balances = fetchSip10FungibleTokens( + account.stxAddress, + fiatCurrency, + network, + stacksNetwork, + ); + finalSipCoinsList = (await fetchSip10Balances()).filter( + (ft) => sip10ManageTokens[ft.principal] !== false, + ); } const totalBalance = calculateTotalBalance({ stxBalance, btcBalance, - ftCoinList, - brcCoinsList: finalBrcCoinList, + sipCoinsList: finalSipCoinsList, + brcCoinsList: finalBrcCoinsList, stxBtcRate, btcFiatRate, hideStx, diff --git a/src/app/hooks/queries/useBtcCoinsBalance.ts b/src/app/hooks/queries/useBtcCoinsBalance.ts deleted file mode 100644 index b68362f01..000000000 --- a/src/app/hooks/queries/useBtcCoinsBalance.ts +++ /dev/null @@ -1,94 +0,0 @@ -import useWalletSelector from '@hooks/useWalletSelector'; -import { - Brc20Token, - FungibleToken, - getBrc20Tokens, - getOrdinalsFtBalance, -} from '@secretkeylabs/xverse-core'; -import { setBrcCoinsDataAction } from '@stores/wallet/actions/actionCreators'; -import { useQuery } from '@tanstack/react-query'; -import { useDispatch } from 'react-redux'; - -export const brc20TokenToFungibleToken = (coin: Brc20Token): FungibleToken => ({ - name: coin.name, - principal: coin.ticker ?? coin.name, - balance: '0', - total_sent: '', - total_received: '', - assetName: coin.name ?? coin.ticker, - visible: false, - ticker: coin.ticker, -}); - -const useBtcCoinBalance = () => { - const dispatch = useDispatch(); - const { ordinalsAddress, network, brcCoinsList, fiatCurrency } = useWalletSelector(); - - // For future lost souls: - // brcCoinsList = current local store - // ordinalsFtBalance = latest brc20 balance - // brc20Tokens = brc20 coin metadata, - // WITH additional supported tokens returned even if not passed - const fetchBrcCoinsBalances = async () => { - try { - const ordinalsFtBalance = await getOrdinalsFtBalance(network.type, ordinalsAddress); - const tickers = ordinalsFtBalance?.map((o) => o.ticker!) ?? []; - const brc20Tokens = await getBrc20Tokens( - network.type, - // workaround for brc20 tokens not being returned - tickers.length ? tickers : ['ORDI'], - fiatCurrency, - ); - - const brcCoinsListMap = new Map(brcCoinsList?.map((token) => [token.ticker, token])); - - const mergedList: FungibleToken[] = ordinalsFtBalance.map((newToken) => { - const existingToken = brcCoinsListMap.get(newToken.ticker); - - const reconstitutedFt = { - ...existingToken, - ...newToken, - // The `visible` property from `xverse-core` defaults to true. - // We override `visible` to ensure that the existing state is preserved. - ...(existingToken ? { visible: existingToken.visible } : {}), - }; - - return reconstitutedFt; - }); - - brc20Tokens?.forEach((b) => { - const existingToken = brcCoinsListMap.get(b.ticker); - const pendingToken = mergedList?.find((m) => m.ticker === b.ticker); - const tokenFiatRate = Number(b?.tokenFiatRate); - - // No duplicates - if (pendingToken) { - pendingToken.tokenFiatRate = tokenFiatRate; - return; - } - - if (existingToken) { - mergedList.push({ - ...existingToken, - tokenFiatRate, - }); - } else { - mergedList.push(brc20TokenToFungibleToken(b)); - } - }); - - dispatch(setBrcCoinsDataAction(mergedList)); - return mergedList; - } catch (e: any) { - return Promise.reject(e); - } - }; - - return useQuery({ - queryKey: [`btc-coins-balance-${ordinalsAddress}`], - queryFn: fetchBrcCoinsBalances, - refetchOnWindowFocus: true, - }); -}; - -export default useBtcCoinBalance; diff --git a/src/app/hooks/queries/useCoinData.ts b/src/app/hooks/queries/useCoinData.ts deleted file mode 100644 index 9af41cfef..000000000 --- a/src/app/hooks/queries/useCoinData.ts +++ /dev/null @@ -1,99 +0,0 @@ -import useNetworkSelector from '@hooks/useNetwork'; -import useWalletSelector from '@hooks/useWalletSelector'; -import { - FungibleToken, - getCoinMetaData, - getCoinsInfo, - getFtData, -} from '@secretkeylabs/xverse-core'; -import { setCoinDataAction } from '@stores/wallet/actions/actionCreators'; -import { useQuery } from '@tanstack/react-query'; -import { InvalidParamsError, handleRetries } from '@utils/query'; -import { useDispatch } from 'react-redux'; - -export const useCoinsData = () => { - const dispatch = useDispatch(); - const { stxAddress, coinsList, fiatCurrency, network } = useWalletSelector(); - const currentNetworkInstance = useNetworkSelector(); - - const fetchCoinData = async () => { - try { - if (!stxAddress) { - throw new InvalidParamsError('No stx address'); - } - - const fungibleTokenList: FungibleToken[] = await getFtData( - stxAddress, - currentNetworkInstance, - ); - const visibleCoins: FungibleToken[] | null = coinsList; - if (visibleCoins) { - visibleCoins.forEach((visibleCoin) => { - const coinToBeUpdated = fungibleTokenList.find( - (ft) => ft.principal === visibleCoin.principal, - ); - if (coinToBeUpdated) coinToBeUpdated.visible = visibleCoin.visible; - else if (visibleCoin.visible) { - visibleCoin.balance = '0'; - fungibleTokenList.push(visibleCoin); - } - }); - } else { - fungibleTokenList.forEach((ft) => { - ft.visible = true; - }); - } - - // getting contract ids of all fts - const contractids: string[] = fungibleTokenList.map((ft) => ft.principal); - - let coinsResponse = await getCoinsInfo(network.type, contractids, fiatCurrency); - if (!coinsResponse) { - coinsResponse = await getCoinMetaData(contractids, currentNetworkInstance); - } - - coinsResponse.forEach((coin) => { - if (!coin.name) { - const coinName = coin.contract.split('.')[1]; - coin.name = coinName; - } - }); - - // update attributes of fungible token list - fungibleTokenList.forEach((ft) => { - coinsResponse!.forEach((coin) => { - if (ft.principal === coin.contract) { - ft.ticker = coin.ticker; - ft.decimals = coin.decimals; - ft.supported = coin.supported; - ft.image = coin.image; - ft.name = coin.name; - ft.tokenFiatRate = coin.tokenFiatRate; - coin.visible = ft.visible; - } - }); - }); - - // sorting the list - moving supported to the top - const supportedFts: FungibleToken[] = []; - const unSupportedFts: FungibleToken[] = []; - fungibleTokenList.forEach((ft) => { - if (ft.supported) supportedFts.push(ft); - else unSupportedFts.push(ft); - }); - const sortedFtList: FungibleToken[] = [...supportedFts, ...unSupportedFts]; - dispatch(setCoinDataAction(sortedFtList, coinsResponse)); - return { sortedFtList, coinsResponse }; - } catch (error: any) { - return Promise.reject(error); - } - }; - - return useQuery({ - queryKey: ['coins_data'], - queryFn: fetchCoinData, - retry: handleRetries, - }); -}; - -export default useCoinsData; diff --git a/src/app/hooks/queries/useTransactions.ts b/src/app/hooks/queries/useTransactions.ts index d03c9746d..f38e9e4eb 100644 --- a/src/app/hooks/queries/useTransactions.ts +++ b/src/app/hooks/queries/useTransactions.ts @@ -22,21 +22,19 @@ export default function useTransactions(coinType: CurrencyTypes, brc20Token: str | (AddressTransactionWithTransfers | MempoolTransaction)[] | Brc20HistoryTransactionData[] > => { + if (coinType === 'FT' && brc20Token) { + return getBrc20History(network.type, ordinalsAddress, brc20Token); + } if (coinType === 'STX' || coinType === 'FT' || coinType === 'NFT') { return getStxAddressTransactions(stxAddress, selectedNetwork, 0, PAGINATION_LIMIT); } if (coinType === 'BTC') { - const btcData = await fetchBtcTransactionsData( + return fetchBtcTransactionsData( btcAddress, ordinalsAddress, btcClient, hasActivatedOrdinalsKey as boolean, ); - return btcData; - } - if (coinType === 'brc20' && brc20Token) { - const brc20Data = await getBrc20History(network.type, ordinalsAddress, brc20Token); - return brc20Data; } return []; }; diff --git a/src/app/hooks/useHasFeature.ts b/src/app/hooks/useHasFeature.ts new file mode 100644 index 000000000..f0262348b --- /dev/null +++ b/src/app/hooks/useHasFeature.ts @@ -0,0 +1,14 @@ +import useWalletSelector from './useWalletSelector'; + +type FeatureId = 'RUNES_SUPPORT'; + +export default function useHasFeature(feature: FeatureId): boolean { + const { network } = useWalletSelector(); + + switch (feature) { + case 'RUNES_SUPPORT': + return network?.type === 'Testnet'; + default: + return false; + } +} diff --git a/src/app/hooks/useResetUserFlow.ts b/src/app/hooks/useResetUserFlow.ts index 7443eb201..24429d686 100644 --- a/src/app/hooks/useResetUserFlow.ts +++ b/src/app/hooks/useResetUserFlow.ts @@ -27,6 +27,7 @@ const userFlowConfig: Record = { '/send-rare-sat': { resetTo: '/nft-dashboard?tab=rareSats' }, '/verify-ledger': { resetTo: '/verify-ledger?mismatch=true' }, '/add-stx-address-ledger': { resetTo: '/add-stx-address-ledger?mismatch=true' }, + '/send-rune': { resetTo: '/' }, }; type UserFlowConfigKey = keyof typeof userFlowConfig; diff --git a/src/app/hooks/useRunesApi.ts b/src/app/hooks/useRunesApi.ts new file mode 100644 index 000000000..afc1ee287 --- /dev/null +++ b/src/app/hooks/useRunesApi.ts @@ -0,0 +1,10 @@ +import { RunesApi } from '@secretkeylabs/xverse-core'; +import { useMemo } from 'react'; +import useWalletSelector from './useWalletSelector'; + +const useRunesApi = () => { + const { network } = useWalletSelector(); + return useMemo(() => new RunesApi(network.type), [network.type]); +}; + +export default useRunesApi; diff --git a/src/app/routes/index.tsx b/src/app/routes/index.tsx index 83ae174e2..015746a25 100644 --- a/src/app/routes/index.tsx +++ b/src/app/routes/index.tsx @@ -49,10 +49,11 @@ import RestoreWallet from '@screens/restoreWallet'; import SendBrc20Screen from '@screens/sendBrc20'; import SendBrc20OneStepScreen from '@screens/sendBrc20OneStep'; import SendBtcScreen from '@screens/sendBtc'; -import SendFtScreen from '@screens/sendFt'; +import SendSip10Screen from '@screens/sendFt'; import SendNft from '@screens/sendNft'; import SendOrdinal from '@screens/sendOrdinal'; import SendRareSat from '@screens/sendRareSat'; +import SendRuneScreen from '@screens/sendRune'; import SendStxScreen from '@screens/sendStx'; import Setting from '@screens/settings'; import BackupWalletScreen from '@screens/settings/backupWallet'; @@ -136,8 +137,8 @@ const router = createHashRouter([ element: , }, { - path: 'send-ft', - element: , + path: 'send-sip10', + element: , }, { path: 'add-stx-address-ledger', @@ -316,7 +317,7 @@ const router = createHashRouter([ element: , }, { - path: 'recover-ordinals', + path: 'restore-ordinals', element: , }, { @@ -348,7 +349,7 @@ const router = createHashRouter([ element: , }, { - path: 'coinDashboard/:coin', + path: 'coinDashboard/:currency', element: , }, { @@ -423,6 +424,10 @@ const router = createHashRouter([ path: 'confirm-btc-tx', element: , }, + { + path: 'send-rune', + element: , + }, { path: 'nft-dashboard', element: ( diff --git a/src/app/screens/coinDashboard/coinHeader.tsx b/src/app/screens/coinDashboard/coinHeader.tsx index ca9404d07..4d1518d6d 100644 --- a/src/app/screens/coinDashboard/coinHeader.tsx +++ b/src/app/screens/coinDashboard/coinHeader.tsx @@ -66,14 +66,15 @@ const BalanceValuesContainer = styled.div({ flexDirection: 'column', }); -const CoinBalanceText = styled.h1((props) => ({ +const CoinBalanceText = styled.p((props) => ({ ...props.theme.headline_l, fontSize: '1.5rem', color: props.theme.colors.white_0, textAlign: 'center', + wordBreak: 'break-all', })); -const FiatAmountText = styled.h1((props) => ({ +const FiatAmountText = styled.p((props) => ({ ...props.theme.headline_category_s, color: props.theme.colors.white_200, fontSize: '0.875rem', @@ -81,7 +82,7 @@ const FiatAmountText = styled.h1((props) => ({ textAlign: 'center', })); -const BalanceTitleText = styled.h1((props) => ({ +const BalanceTitleText = styled.p((props) => ({ ...props.theme.body_medium_m, color: props.theme.colors.white_400, textAlign: 'center', @@ -278,12 +279,18 @@ export default function CoinHeader(props: CoinBalanceProps) { url: chrome.runtime.getURL('options.html#/send-stx'), }); return; - case 'FT': + default: + break; + } + switch (fungibleToken?.protocol) { + case 'stacks': await chrome.tabs.create({ - url: chrome.runtime.getURL(`options.html#/send-ft?coinTicker=${fungibleToken?.ticker}`), + url: chrome.runtime.getURL( + `options.html#/send-sip10?coinTicker=${fungibleToken?.ticker}`, + ), }); return; - case 'brc20': + case 'brc-20': // TODO replace with send-brc20-one-step route, when ledger support is ready await chrome.tabs.create({ url: chrome.runtime.getURL( @@ -291,30 +298,61 @@ export default function CoinHeader(props: CoinBalanceProps) { ), }); return; + case 'runes': + await chrome.tabs.create({ + url: chrome.runtime.getURL( + `options.html#/send-rune?coinTicker=${fungibleToken?.ticker}`, + ), + }); + return; default: break; } } - if (coin === 'STX' || coin === 'BTC') { - navigate(`/send-${coin}`); - } else if (coin === 'FT') { - navigate('/send-ft', { - state: { - fungibleToken, - }, - }); - } else if (coin === 'brc20') { - navigate('/send-brc20-one-step', { - state: { - fungibleToken, - }, - }); + switch (coin) { + case 'BTC': + case 'STX': + navigate(`/send-${coin}`); + break; + default: + break; + } + switch (fungibleToken?.protocol) { + case 'stacks': + navigate('/send-sip10', { + state: { + fungibleToken, + }, + }); + break; + case 'brc-20': + navigate('/send-brc20-one-step', { + state: { + fungibleToken, + }, + }); + break; + case 'runes': + navigate('/send-rune', { + state: { + fungibleToken, + }, + }); + break; + default: + break; } }; const getDashboardTitle = () => { - if (fungibleToken) { - return `${getFtTicker(fungibleToken)} ${t('BALANCE')}`; + if (fungibleToken?.name) { + return `${fungibleToken.name} ${t('BALANCE')}`; + } + if (coin === 'STX') { + return `Stacks ${t('BALANCE')}`; + } + if (coin === 'BTC') { + return `Bitcoin ${t('BALANCE')}`; } if (coin) { return `${coin} ${t('BALANCE')}`; @@ -329,7 +367,11 @@ export default function CoinHeader(props: CoinBalanceProps) { text={t('VERIFY_ADDRESS_ON_LEDGER')} onPress={async () => { await chrome.tabs.create({ - url: chrome.runtime.getURL(`options.html#/verify-ledger?currency=${coin}`), + url: chrome.runtime.getURL( + `options.html#/verify-ledger?currency=${ + !fungibleToken ? coin : fungibleToken?.protocol === 'stacks' ? 'STX' : 'ORD' + }`, + ), }); }} /> @@ -338,7 +380,11 @@ export default function CoinHeader(props: CoinBalanceProps) { transparent text={t('VIEW_ADDRESS')} onPress={() => { - navigate(`/receive/${coin}`); + navigate( + `/receive/${ + !fungibleToken ? coin : fungibleToken?.protocol === 'stacks' ? 'STX' : 'ORD' + }`, + ); }} /> @@ -348,14 +394,19 @@ export default function CoinHeader(props: CoinBalanceProps) { {getDashboardTitle()} - {coin === 'brc20' && BRC-20} - {fungibleToken?.protocol === 'stacks' && SIP-10} + {fungibleToken?.protocol && ( + + {fungibleToken?.protocol === 'stacks' + ? 'SIP-10' + : fungibleToken.protocol?.toUpperCase()} + + )} navigate(coin === 'brc20' ? '/receive/ORD' : `/receive/${coin}`)} + // RUNES & BRC20s => ordinal wallet, SIP-10 => STX wallet + onPress={() => + navigate(`/receive/${fungibleToken?.protocol === 'stacks' ? 'STX' : 'ORD'}`) + } /> )} diff --git a/src/app/screens/coinDashboard/index.tsx b/src/app/screens/coinDashboard/index.tsx index 637eeebd7..067c98b8b 100644 --- a/src/app/screens/coinDashboard/index.tsx +++ b/src/app/screens/coinDashboard/index.tsx @@ -2,9 +2,10 @@ import linkIcon from '@assets/img/linkIcon.svg'; import CopyButton from '@components/copyButton'; import BottomBar from '@components/tabBar'; import TopRow from '@components/topRow'; +import { useVisibleBrc20FungibleTokens } from '@hooks/queries/ordinals/useGetBrc20FungibleTokens'; +import { useVisibleRuneFungibleTokens } from '@hooks/queries/runes/useGetRuneFungibleTokens'; +import { useVisibleSip10FungibleTokens } from '@hooks/queries/stx/useGetSip10FungibleTokens'; import useBtcWalletData from '@hooks/queries/useBtcWalletData'; -import useWalletSelector from '@hooks/useWalletSelector'; -import { FungibleToken } from '@secretkeylabs/xverse-core'; import { CurrencyTypes } from '@utils/constants'; import { getExplorerUrl } from '@utils/helper'; import { useState } from 'react'; @@ -108,11 +109,12 @@ export default function CoinDashboard() { const { t } = useTranslation('translation', { keyPrefix: 'COIN_DASHBOARD_SCREEN' }); const navigate = useNavigate(); const [showFtContractDetails, setShowFtContractDetails] = useState(false); - const { coin } = useParams(); + const { currency } = useParams(); const [searchParams] = useSearchParams(); - const { coinsList, brcCoinsList } = useWalletSelector(); - const ftAddress = searchParams.get('ft'); - const brc20FtName = searchParams.get('brc20ft'); + const { visible: runesCoinsList } = useVisibleRuneFungibleTokens(); + const { visible: sip10CoinsList } = useVisibleSip10FungibleTokens(); + const { visible: brc20CoinsList } = useVisibleBrc20FungibleTokens(); + const ftKey = searchParams.get('ftKey'); useBtcWalletData(); @@ -120,27 +122,22 @@ export default function CoinDashboard() { navigate(-1); }; - const ft = coinsList?.find((ftCoin) => ftCoin.principal === ftAddress); - let brc20Ft: FungibleToken | undefined; - if (brc20FtName) { - brc20Ft = brcCoinsList?.find((brc20FtCoin) => brc20FtCoin.principal === brc20FtName); - } + const selectedFt = + sip10CoinsList.find((ft) => ft.principal === ftKey) ?? + brc20CoinsList.find((ft) => ft.principal === ftKey) ?? + runesCoinsList.find((ft) => ft.principal === ftKey); - const openContractDeployment = () => { - window.open(getExplorerUrl(ft?.principal as string), '_blank'); - }; + const protocol = selectedFt?.protocol; - const onContractClick = () => { - setShowFtContractDetails(true); - }; + const openContractDeployment = () => + window.open(getExplorerUrl(selectedFt?.principal as string), '_blank'); - const handleCopyContractAddress = () => { - navigator.clipboard.writeText(ft?.principal as string); - }; + const onContractClick = () => setShowFtContractDetails(true); - const onTransactionsClick = () => { - setShowFtContractDetails(false); - }; + const handleCopyContractAddress = () => + navigator.clipboard.writeText(selectedFt?.principal as string); + + const onTransactionsClick = () => setShowFtContractDetails(false); const formatAddress = (addr: string): string => addr ? `${addr.substring(0, 20)}...${addr.substring(addr.length - 20, addr.length)}` : ''; @@ -149,24 +146,34 @@ export default function CoinDashboard() { <> - - {ft && ( + + {protocol === 'stacks' && ( - - )} - {ft && showFtContractDetails ? ( + {protocol === 'stacks' && showFtContractDetails && (

{t('FT_CONTRACT_PREFIX')}

- {formatAddress(ft?.principal as string)} + + {formatAddress(selectedFt?.principal as string)} + - + @@ -175,11 +182,12 @@ export default function CoinDashboard() {
- ) : ( + )} + {protocol !== 'runes' && ( )}
diff --git a/src/app/screens/coinDashboard/transactionsHistoryList.tsx b/src/app/screens/coinDashboard/transactionsHistoryList.tsx index cf8cb65fb..9fa5c638e 100644 --- a/src/app/screens/coinDashboard/transactionsHistoryList.tsx +++ b/src/app/screens/coinDashboard/transactionsHistoryList.tsx @@ -1,4 +1,4 @@ -import BtcTransactionHistoryItem from '@components/transactions/btcTransaction'; +import BtcOrBrc20TransactionHistoryItem from '@components/transactions/btcOrBrc20Transaction'; import StxTransactionHistoryItem from '@components/transactions/stxTransaction'; import useTransactions from '@hooks/queries/useTransactions'; import useBtcClient from '@hooks/useBtcClient'; @@ -231,10 +231,10 @@ export default function TransactionsHistoryList(props: TransactionsHistoryListPr {groupedTxs[group].map((transaction) => { if (wallet && (isBtcTransaction(transaction) || isBrc20Transaction(transaction))) { return ( - ); } diff --git a/src/app/screens/confirmBrc20Transaction/recipientCard.tsx b/src/app/screens/confirmBrc20Transaction/recipientCard.tsx index b131a3a17..456ab383c 100644 --- a/src/app/screens/confirmBrc20Transaction/recipientCard.tsx +++ b/src/app/screens/confirmBrc20Transaction/recipientCard.tsx @@ -35,7 +35,7 @@ function RecipientCard({ address, amountBrc20, amountSats, fungibleToken }: Reci return ( } + icon={} amountLabel={t('CONFIRM_TRANSACTION.AMOUNT')} amount={ { - navigate('/recover-ordinals', { + navigate('/restore-ordinals', { state: { isRestoreFundFlow: true }, }); }; diff --git a/src/app/screens/confirmFtTransaction/index.tsx b/src/app/screens/confirmFtTransaction/index.tsx index 9b62dee13..8e388e18a 100644 --- a/src/app/screens/confirmFtTransaction/index.tsx +++ b/src/app/screens/confirmFtTransaction/index.tsx @@ -87,7 +87,7 @@ function ConfirmFtTransaction() { }; const handleBackButtonClick = () => { - navigate('/send-ft', { + navigate('/send-sip10', { state: { recipientAddress: recepientAddress, amountToSend: amount.toString(), diff --git a/src/app/screens/confirmInscriptionRequest/index.tsx b/src/app/screens/confirmInscriptionRequest/index.tsx index 988ee8fe5..6ecc408ce 100644 --- a/src/app/screens/confirmInscriptionRequest/index.tsx +++ b/src/app/screens/confirmInscriptionRequest/index.tsx @@ -19,13 +19,13 @@ import Brc20Tile from '@screens/ordinals/brc20Tile'; import CollapsableContainer from '@screens/signatureRequest/collapsableContainer'; import { BtcTransactionBroadcastResponse, - getBtcFiatEquivalent, - parseOrdinalTextContentData, Recipient, ResponseError, + SignedBtcTx, + getBtcFiatEquivalent, + parseOrdinalTextContentData, satsToBtc, signBtcTransaction, - SignedBtcTx, } from '@secretkeylabs/xverse-core'; import { useMutation } from '@tanstack/react-query'; import { isLedgerAccount } from '@utils/helper'; @@ -224,7 +224,7 @@ function ConfirmInscriptionRequest() { }; const onClick = () => { - navigate('/recover-ordinals'); + navigate('/restore-ordinals'); }; useEffect(() => { diff --git a/src/app/screens/home/balanceCard/index.tsx b/src/app/screens/home/balanceCard/index.tsx index dacfde673..04290ef23 100644 --- a/src/app/screens/home/balanceCard/index.tsx +++ b/src/app/screens/home/balanceCard/index.tsx @@ -1,4 +1,6 @@ import BarLoader from '@components/barLoader'; +import { useVisibleBrc20FungibleTokens } from '@hooks/queries/ordinals/useGetBrc20FungibleTokens'; +import { useVisibleSip10FungibleTokens } from '@hooks/queries/stx/useGetSip10FungibleTokens'; import useAccountBalance from '@hooks/queries/useAccountBalance'; import useWalletSelector from '@hooks/useWalletSelector'; import { currencySymbolMap } from '@secretkeylabs/xverse-core'; @@ -77,20 +79,20 @@ function BalanceCard(props: BalanceCardProps) { btcBalance, btcAddress, hideStx, - coinsList, selectedAccount, accountBalances, - brcCoinsList, } = useWalletSelector(); const { setAccountBalance } = useAccountBalance(); const { isLoading, isRefetching } = props; const oldTotalBalance = accountBalances[btcAddress]; + const { visible: sip10CoinsList } = useVisibleSip10FungibleTokens(); + const { visible: brc20CoinsList } = useVisibleBrc20FungibleTokens(); const balance = calculateTotalBalance({ stxBalance, btcBalance, - ftCoinList: coinsList, - brcCoinsList, + sipCoinsList: sip10CoinsList, + brcCoinsList: brc20CoinsList, btcFiatRate, stxBtcRate, hideStx, diff --git a/src/app/screens/home/coinSelectModal/index.tsx b/src/app/screens/home/coinSelectModal/index.tsx index d15586d18..8a37c4d6c 100644 --- a/src/app/screens/home/coinSelectModal/index.tsx +++ b/src/app/screens/home/coinSelectModal/index.tsx @@ -1,5 +1,3 @@ -import IconBitcoin from '@assets/img/dashboard/bitcoin_icon.svg'; -import IconStacks from '@assets/img/dashboard/stx_icon.svg'; import BottomModal from '@components/bottomModal'; import TokenTile from '@components/tokenTile'; import useWalletSelector from '@hooks/useWalletSelector'; @@ -53,7 +51,6 @@ function CoinSelectModal({ ({ })); const ButtonText = styled.div((props) => ({ - ...props.theme.body_xs, + ...props.theme.typography.body_s, fontWeight: 700, color: props.theme.colors.white_0, textAlign: 'center', @@ -119,7 +119,12 @@ const Icon = styled.img({ const MergedIcon = styled.img({ width: 40, - height: 40, + height: 24, +}); + +const MergedOrdinalsIcon = styled.img({ + width: 64, + height: 24, }); const VerifyOrViewContainer = styled.div((props) => ({ @@ -153,7 +158,7 @@ const ModalTitle = styled.div((props) => ({ const ModalDescription = styled.div((props) => ({ ...props.theme.typography.body_m, - color: props.theme.colors.white['200'], + color: props.theme.colors.white_200, marginBottom: props.theme.spacing(16), textAlign: 'center', })); @@ -183,12 +188,10 @@ function Home() { const [isBtcReceiveAlertVisible, setIsBtcReceiveAlertVisible] = useState(false); const [isOrdinalReceiveAlertVisible, setIsOrdinalReceiveAlertVisible] = useState(false); const { - coinsList, stxAddress, btcAddress, ordinalsAddress, selectedAccount, - brcCoinsList, showBtcReceiveAlert, showOrdinalReceiveAlert, showDataCollectionAlert, @@ -204,9 +207,21 @@ function Home() { stxAddress ? useStxWalletData() : { isLoading: false, isRefetching: false }; const { isLoading: loadingBtcWalletData, isRefetching: refetchingBtcWalletData } = useBtcWalletData(); - const { isLoading: loadingCoinData, isRefetching: refetchingCoinData } = useCoinsData(); - const { isLoading: loadingBtcCoinData, isRefetching: refetchingBtcCoinData } = - useBtcCoinBalance(); + const { + visible: sip10CoinsList, + isLoading: loadingStxCoinData, + isRefetching: refetchingStxCoinData, + } = useVisibleSip10FungibleTokens(); + const { + visible: brc20CoinsList, + isLoading: loadingBtcCoinData, + isRefetching: refetchingBtcCoinData, + } = useVisibleBrc20FungibleTokens(); + const { + visible: runesCoinsList, + isLoading: loadingRunesData, + isRefetching: refetchingRunesData, + } = useVisibleRuneFungibleTokens(); useFeeMultipliers(); useCoinRates(); useAppConfig(); @@ -241,8 +256,7 @@ function Home() { }; function getCoinsList() { - const list = coinsList ? coinsList?.filter((ft) => ft.visible) : []; - return brcCoinsList ? list.concat(brcCoinsList) : list; + return [...sip10CoinsList, ...brc20CoinsList, ...runesCoinsList]; } const handleManageTokenListOnClick = () => { @@ -277,37 +291,49 @@ function Home() { navigate('/receive/STX'); }; - const onSendFtSelect = async (coin: FungibleToken) => { - if (coin.protocol === 'brc-20') { - if (isLedgerAccount(selectedAccount)) { - if (!isInOptions()) { - await chrome.tabs.create({ - // TODO replace with send-brc20-one-step route, when ledger support is ready - url: chrome.runtime.getURL(`options.html#/send-brc20?coinName=${coin.name}`), - }); - return; - } - navigate(`send-brc20?coinName=${coin.name}`); + const onSendFtSelect = async (fungibleToken: FungibleToken) => { + if (fungibleToken.protocol === 'brc-20') { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { + await chrome.tabs.create({ + // TODO replace with send-brc20-one-step route, when ledger support is ready + url: chrome.runtime.getURL(`options.html#/send-brc20?coinTicker=${fungibleToken.ticker}`), + }); return; } - navigate('send-brc20-one-step', { + navigate('/send-brc20-one-step', { state: { - fungibleToken: coin, + fungibleToken, }, }); return; } - if (isLedgerAccount(selectedAccount) && !isInOptions()) { - await chrome.tabs.create({ - url: chrome.runtime.getURL(`options.html#/send-ft?coinTicker=${coin.ticker}`), + if (fungibleToken.protocol === 'stacks') { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { + await chrome.tabs.create({ + // TODO - check why use coin ticker when its kinda risky? shouldnt fungibalToken.principal be the main identifier? + url: chrome.runtime.getURL(`options.html#/send-sip10?coinTicker=${fungibleToken.ticker}`), + }); + return; + } + navigate('/send-sip10', { + state: { + fungibleToken, + }, + }); + } + if (fungibleToken.protocol === 'runes') { + if (isLedgerAccount(selectedAccount) && !isInOptions()) { + await chrome.tabs.create({ + url: chrome.runtime.getURL(`options.html#/send-rune?coinTicker=${fungibleToken.ticker}`), + }); + return; + } + navigate('/send-rune', { + state: { + fungibleToken, + }, }); - return; } - navigate('send-ft', { - state: { - fungibleToken: coin, - }, - }); }; const onBuyStxClick = () => { @@ -334,15 +360,11 @@ function Home() { if (showBtcReceiveAlert) setIsBtcReceiveAlertVisible(true); }; - const handleTokenPressed = (token: { - coin: CurrencyTypes; - ft: string | undefined; - brc20Ft?: string; - }) => { - if (token.brc20Ft) { - navigate(`/coinDashboard/${token.coin}?brc20ft=${token.brc20Ft}`); + const handleTokenPressed = (currency: CurrencyTypes, ftKey?: string) => { + if (ftKey) { + navigate(`/coinDashboard/${currency}?ftKey=${ftKey}`); } else { - navigate(`/coinDashboard/${token.coin}?ft=${token.ft}`); + navigate(`/coinDashboard/${currency}`); } }; @@ -368,14 +390,14 @@ function Home() { - + {stxAddress && ( @@ -444,6 +466,7 @@ function Home() { }; const showSwaps = !isLedgerAccount(selectedAccount) && network.type !== 'Testnet'; + const showRunes = useHasFeature('RUNES_SUPPORT'); return ( <> @@ -460,8 +483,9 @@ function Home() { isRefetching={ refetchingBtcCoinData || refetchingBtcWalletData || - refetchingCoinData || - refetchingStxWalletData + refetchingStxCoinData || + refetchingStxWalletData || + refetchingRunesData } /> @@ -488,9 +512,8 @@ function Home() { )} @@ -498,44 +521,49 @@ function Home() { )} - {(!!coinsList?.length || !!brcCoinsList?.length) && ( - - {!!stxAddress && - coinsList - ?.filter((ft) => ft.visible) - .map((coin) => ( - - ))} - {brcCoinsList - ?.filter((ft) => ft.visible) - .map((coin) => ( - - ))} - - )} + + {!!stxAddress && + sip10CoinsList.map((coin) => ( + + ))} + {brc20CoinsList.map((coin) => ( + + ))} + {showRunes && + runesCoinsList.map((coin) => ( + + ))} + ({ - marginRight: props.theme.spacing(7), - width: 32, - height: 32, - resizeMode: 'stretch', - borderRadius: '50%', -})); - const CustomSwitch = styled(Switch)` .react-switch-handle { background-color: ${({ checked }) => @@ -35,37 +27,11 @@ const CustomSwitch = styled(Switch)` } `; -const TickerIconContainer = styled.div((props) => ({ - display: 'flex', - marginRight: props.theme.spacing(7), - height: '32px', - width: '32px', - borderRadius: props.theme.radius(3), - alignItems: 'center', - justifyContent: 'center', - backgroundColor: props.color, - flexShrink: 0, -})); - -const TickerText = styled.h1((props) => ({ - ...props.theme.typography.body_s, - color: props.theme.colors.white_0, - textAlign: 'center', - wordBreak: 'break-all', - fontSize: 10, - width: '100%', -})); - -const SelectedCoinTitleText = styled.h1((props) => ({ - ...props.theme.typography.body_bold_m, - color: props.theme.colors.white_0, - textAlign: 'center', -})); - -const UnSelectedCoinTitleText = styled.h1((props) => ({ - ...props.theme.typography.body_m, - color: props.theme.colors.white_400, +const CoinTitleText = styled.p<{ isEnabled?: boolean }>((props) => ({ + ...props.theme.typography[props.isEnabled ? 'body_bold_m' : 'body_m'], + color: props.theme.colors[props.isEnabled ? 'white_0' : 'white_400'], textAlign: 'center', + marginLeft: props.theme.space.m, })); interface Props { @@ -73,38 +39,24 @@ interface Props { name: string; image?: string; ticker?: string; + protocol?: string; disabled: boolean; toggled(enabled: boolean, coinName: string, coinKey: string): void; enabled?: boolean; } -function CoinItem({ id, name, image, ticker, disabled, toggled, enabled }: Props) { +function CoinItem({ id, name, image, ticker, protocol, disabled, toggled, enabled }: Props) { const [isEnabled, setIsEnabled] = useState(enabled); const toggleSwitch = () => { setIsEnabled((previousState) => !previousState); toggled(!isEnabled, name, id); }; - function getTickerName() { - return !ticker && name ? getTicker(name) : ticker; - } - const background = stc(getTickerName()); - return ( - {image ? ( - - ) : ( - - {getTickerName()} - - )} - {isEnabled ? ( - {name} - ) : ( - {name} - )} + + {name} ({ marginBottom: props.theme.spacing(16), })); +const RunesContainer = styled.div((props) => ({ + marginTop: props.theme.spacing(24), + marginRight: props.theme.spacing(5), + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', +})); + +const ErrorsText = styled.p((props) => ({ + ...props.theme.typography.body_bold_m, + color: props.theme.colors.white_200, + marginTop: props.theme.spacing(16), + marginBottom: 'auto', + textAlign: 'center', +})); + +const RunesComingSoon = styled.img({ + width: '70%', +}); + function Stacks() { const { hideStx } = useWalletSelector(); const { toggleStxVisibility } = useWalletReducer(); @@ -98,67 +123,89 @@ function Stacks() { ); } -enum Protocols { - SIP_10 = 'SIP-10', - BRC_20 = 'BRC-20', -} - function ManageTokens() { const { t } = useTranslation('translation', { keyPrefix: 'TOKEN_SCREEN' }); - const { coinsList, coins, brcCoinsList, selectedAccount } = useSelector( - (state: StoreState) => state.walletState, - ); - const [selectedProtocol, setSelectedProtocol] = useState( - selectedAccount?.stxAddress ? Protocols.SIP_10 : Protocols.BRC_20, - ); + const { sip10ManageTokens, brc20ManageTokens, runesManageTokens, selectedAccount } = + useWalletSelector(); + const { data: runesList, isError: runeError } = useGetRuneFungibleTokens(); + const { data: sip10List, isError: sip10Error } = useGetSip10FungibleTokens(); + const { data: brc20List, isError: brc20Error } = useGetBrc20FungibleTokens(); + + const [selectedProtocol, setSelectedProtocol] = useState( + selectedAccount?.stxAddress ? 'stacks' : 'brc-20', + ); + const showRunes = useHasFeature('RUNES_SUPPORT'); const navigate = useNavigate(); const dispatch = useDispatch(); - const toggled = (isEnabled: boolean, coinName, coinKey) => { - /* if coins exists in list of fungible token, update the visible property otherwise - add coin in list if coin is set to visible */ - - const coinToBeUpdated = - coinsList?.find((ft) => ft.principal === coinKey) ?? - brcCoinsList?.find((ft) => ft.principal === coinKey); - - if (coinToBeUpdated) { - coinToBeUpdated.visible = isEnabled; - } else if (isEnabled) { - const coinToBeAdded: FungibleToken = { - name: coinName, - visible: true, - principal: coinKey, - balance: '0', - total_sent: '', - total_received: '', - assetName: '', - }; - if (selectedProtocol === Protocols.SIP_10) { - coinsList?.push(coinToBeAdded); - } else if (selectedProtocol === Protocols.BRC_20) { - brcCoinsList?.push(coinToBeAdded); - } - } - - if (coinsList && selectedProtocol === Protocols.SIP_10) { - const modifiedCoinsList = [...coinsList]; - dispatch(FetchUpdatedVisibleCoinListAction(modifiedCoinsList)); + const toggled = (isEnabled: boolean, _coinName: string, coinKey: string) => { + const runeFt = runesList?.find((ft) => ft.principal === coinKey); + const sip10Ft = sip10List?.find((ft) => ft.principal === coinKey); + const brc20Ft = brc20List?.find((ft) => ft.principal === coinKey); + + if (selectedProtocol === 'runes' && runeFt) { + dispatch(setRunesManageTokensAction({ principal: coinKey, isEnabled })); + } else if (selectedProtocol === 'stacks' && sip10Ft) { + dispatch(setSip10ManageTokensAction({ principal: coinKey, isEnabled })); + } else if (selectedProtocol === 'brc-20' && brc20Ft) { + dispatch(setBrc20ManageTokensAction({ principal: coinKey, isEnabled })); } + }; - if (brcCoinsList && selectedProtocol === Protocols.BRC_20) { - const modifiedCoinsList = [...brcCoinsList]; - dispatch(setBrcCoinsDataAction(modifiedCoinsList)); + const handleBackButtonClick = () => navigate('/'); + + const getCoinsList = () => { + let coins: FungibleToken[]; + let error: boolean; + switch (selectedProtocol) { + case 'stacks': + coins = (sip10List ?? []).map((ft) => ({ + ...ft, + visible: sip10ManageTokens[ft.principal] ?? ft.visible, + })); + error = sip10Error; + break; + case 'brc-20': + coins = (brc20List ?? []).map((ft) => ({ + ...ft, + visible: brc20ManageTokens[ft.principal] ?? ft.visible, + })); + error = brc20Error; + break; + case 'runes': + coins = (runesList ?? []).map((ft) => ({ + ...ft, + visible: runesManageTokens[ft.principal] ?? ft.visible, + })); + error = runeError; + break; + default: + coins = []; + error = false; } - }; - const handleBackButtonClick = () => { - navigate('/'); + if (error) return {t('FAILED_TO_FETCH')}; + return ( + <> + {selectedProtocol === 'stacks' && } + {coins.map((coin: FungibleToken) => ( + + ))} + {!coins.length && {t('NO_COINS')}} + + ); }; - const selectedCoins = selectedProtocol === Protocols.SIP_10 ? coins : brcCoinsList; - return ( <> @@ -169,36 +216,33 @@ function ManageTokens() { {selectedAccount?.stxAddress && ( )} + - {selectedProtocol === Protocols.SIP_10 && } - {selectedCoins?.map((coin: FungibleToken | Coin) => { - const coinId = 'principal' in coin ? coin.principal : coin.contract; - return ( - - ); - })} + {selectedProtocol === 'runes' && !showRunes ? ( + + + + ) : ( + getCoinsList() + )} diff --git a/src/app/screens/nftDashboard/collectiblesTabs.tsx b/src/app/screens/nftDashboard/collectiblesTabs.tsx index 63508ac86..e470ba96f 100644 --- a/src/app/screens/nftDashboard/collectiblesTabs.tsx +++ b/src/app/screens/nftDashboard/collectiblesTabs.tsx @@ -1,17 +1,13 @@ import ActionButton from '@components/button'; +import { StyledBarLoader, TilesSkeletonLoader } from '@components/tilesSkeletonLoader'; import WrenchErrorMessage from '@components/wrenchErrorMessage'; -import { - Bundle, - mapRareSatsAPIResponseToBundle, - UtxoOrdinalBundle, -} from '@secretkeylabs/xverse-core'; +import { Bundle, mapRareSatsAPIResponseToBundle } from '@secretkeylabs/xverse-core'; import { StyledP, StyledTab, StyledTabList } from '@ui-library/common.styled'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { TabPanel, Tabs } from 'react-tabs'; import styled from 'styled-components'; -import { StyledBarLoader, TilesSkeletonLoader } from '../../components/tilesSkeletonLoader'; import Notice from './notice'; import RareSatsTabGridItem from './rareSatsTabGridItem'; import type { NftDashboardState } from './useNftDashboard'; @@ -246,7 +242,7 @@ export default function CollectiblesTabs({ rareSatsQuery.data?.pages ?.map((page) => page?.results) .flat() - .map((utxo: UtxoOrdinalBundle) => mapRareSatsAPIResponseToBundle(utxo)) + .map((utxo) => mapRareSatsAPIResponseToBundle(utxo)) .map((bundle: Bundle) => ( ({ - ...props.theme.headline_s, - textAlign: 'center', +const OuterContainer = styled.div((props) => ({ + ...props.theme.scrollbar, + display: 'flex', + flexDirection: 'column', + flex: 1, + paddingLeft: props.theme.spacing(4), + paddingRight: props.theme.spacing(4), })); -const ReceiveScreenText = styled.h1((props) => ({ - ...props.theme.body_m, - textAlign: 'center', - marginTop: props.theme.spacing(3), - color: props.theme.colors.white_200, +const AddressContainer = styled.div({ + display: 'flex', + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'flex-start', + columnGap: 16, +}); + +const Button = styled.button((props) => ({ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + marginLeft: props.theme.spacing(3), + padding: 12, + background: props.theme.colors.elevation2, + borderRadius: 24, + width: 40, + height: 40, })); -const BnsNameText = styled.h1((props) => ({ - ...props.theme.body_bold_l, +const TopTitleText = styled.h1((props) => ({ + ...props.theme.typography.headline_s, textAlign: 'center', - marginBottom: 2, + marginTop: props.theme.spacing(12), })); -const Container = styled.div({ - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - marginTop: 16, - flex: 1, -}); - -const AddressContainer = styled.div((props) => ({ - marginLeft: props.theme.spacing(24), - marginRight: props.theme.spacing(24), +const DescriptionText = styled.p((props) => ({ + ...props.theme.typography.body_m, + textAlign: 'center', + color: props.theme.colors.white_200, + marginTop: props.theme.spacing(6), })); -const CopyContainer = styled.div((props) => ({ +const BnsNameText = styled.h1((props) => ({ display: 'flex', - flexDirection: 'column', - alignItems: 'center', - width: 328, - justifyContent: 'center', - marginTop: props.theme.spacing(11), + flexDirection: 'row', + justifyContent: 'flex-start', + alignItems: 'flex-start', + ...props.theme.typography.body_bold_l, + textAlign: 'center', + marginBottom: 4, })); -const QRCodeContainer = styled.div((props) => ({ +const QRCodeContainer = styled.div<{ marginBottom: number }>((props) => ({ display: 'flex', - aspectRatio: 1, - backgroundColor: props.theme.colors.white_0, + backgroundColor: props.theme.colors.white['0'], justifyContent: 'center', alignItems: 'center', - borderRadius: 8, - padding: props.theme.spacing(5), + borderRadius: props.theme.radius(1), + width: 159, + height: 159, + alignSelf: 'center', marginTop: props.theme.spacing(15), - marginBottom: props.theme.spacing(12), + marginBottom: props.marginBottom, })); const AddressText = styled.h1((props) => ({ - ...props.theme.body_m, + ...props.theme.typography.body_m, textAlign: 'center', color: props.theme.colors.white_200, wordBreak: 'break-all', @@ -89,11 +90,10 @@ const BottomBarContainer = styled.div({ marginTop: 22, }); -const InfoAlertContainer = styled.div({ - width: '100%', -}); +type SupportedAddresses = 'BTC' | 'STX' | 'ORD'; +const validAddresses: SupportedAddresses[] = ['BTC', 'STX', 'ORD']; -function Receive(): JSX.Element { +function Receive() { const { t } = useTranslation('translation', { keyPrefix: 'RECEIVE' }); const [addressCopied, setAddressCopied] = useState(false); const [isBtcReceiveAlertVisible, setIsBtcReceiveAlertVisible] = useState(false); @@ -108,109 +108,96 @@ function Receive(): JSX.Element { showOrdinalReceiveAlert, } = useWalletSelector(); - const { currency } = useParams(); - - const getAddress = () => { - switch (currency) { - case 'STX': - return stxAddress; - case 'BTC': - return btcAddress; - case 'FT': - return stxAddress; - case 'ORD': - return ordinalsAddress; - default: - return ''; - } - }; - const handleBackButtonClick = () => { - navigate(-1); + const { currency } = useParams<{ currency: SupportedAddresses }>(); + + const renderData: Record< + SupportedAddresses, + { address: string; title: string; desc: string; icon: string; gradient: string } + > = { + BTC: { + address: btcAddress, + title: t('BTC_ADDRESS'), + desc: t('BTC_RECEIVE_MESSAGE'), + icon: BtcIcon, + gradient: '#F2A90A', + }, + STX: { + address: stxAddress, + title: t('STX_ADDRESS'), + desc: t('STX_RECEIVE_MESSAGE'), + icon: StxIcon, + gradient: '#7B61FF', + }, + ORD: { + address: ordinalsAddress, + title: t('ORDINAL_ADDRESS'), + desc: t('ORDINALS_RECEIVE_MESSAGE'), + icon: OrdinalIcon, + gradient: '#61FF8D', + }, }; - const renderHeading = () => { - if (currency === 'BTC') { - return {t('BTC_ADDRESS')}; - } - if (currency === 'ORD') { - return {t('ORDINAL_ADDRESS')}; - } - return {t('STX_ADDRESS')}; - }; + const showBnsName = currency === 'STX' && !!selectedAccount?.bnsName; - const onReceiveAlertClose = () => { - setIsBtcReceiveAlertVisible(false); - }; + const handleBackButtonClick = () => navigate(-1); - const onOrdinalReceiveAlertClose = () => { - setIsOrdinalReceiveAlertVisible(false); - }; + // this should not happen but this is the optimal render to allow back navigation + if (!currency || !validAddresses.includes(currency)) { + return ( + <> + + + + + + ); + } const handleOnClick = () => { - navigator.clipboard.writeText(getAddress()); + navigator.clipboard.writeText(renderData[currency].address); setAddressCopied(true); - if (currency === 'BTC' && showBtcReceiveAlert) { - setIsBtcReceiveAlertVisible(true); - } - if (currency === 'ORD' && showOrdinalReceiveAlert) { - setIsOrdinalReceiveAlertVisible(true); - } + if (currency === 'BTC' && showBtcReceiveAlert) setIsBtcReceiveAlertVisible(true); + if (currency === 'ORD' && showOrdinalReceiveAlert) setIsOrdinalReceiveAlertVisible(true); }; - // TODO: Shift UpdatedReceive logic in this file and handle STX & BTC UI - return currency === 'ORD' ? ( - - ) : ( + return ( <> - - {renderHeading()} - {currency !== 'BTC' && currency !== 'ORD' && ( - {t('STX_ADDRESS_DESC')} - )} - - - - - {currency !== 'BTC' && currency !== 'ORD' && !!selectedAccount?.bnsName && ( - {selectedAccount?.bnsName} - )} - - {getAddress()} - - - - {currency === 'ORD' && ( - - - - )} - {currency === 'BTC' && ( - - - - )} - {addressCopied ? ( - - ) : ( - - )} - + {renderData[currency].title} + {renderData[currency].desc} + + + + {showBnsName && {selectedAccount?.bnsName}} + + {renderData[currency].address} + + + {isBtcReceiveAlertVisible && ( - + setIsBtcReceiveAlertVisible(false)} /> )} {isOrdinalReceiveAlertVisible && ( - + setIsOrdinalReceiveAlertVisible(false)} + /> )} ); diff --git a/src/app/screens/receive/updatedReceiveScreen.tsx b/src/app/screens/receive/updatedReceiveScreen.tsx deleted file mode 100644 index f8811f7ea..000000000 --- a/src/app/screens/receive/updatedReceiveScreen.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import Ordinal from '@assets/img/receive_ordinals_image.svg'; -import ShowBtcReceiveAlert from '@components/showBtcReceiveAlert'; -import ShowOrdinalReceiveAlert from '@components/showOrdinalReceiveAlert'; -import BottomTabBar from '@components/tabBar'; -import TopRow from '@components/topRow'; -import useWalletSelector from '@hooks/useWalletSelector'; -import { Check, Copy } from '@phosphor-icons/react'; -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { Tooltip } from 'react-tooltip'; -import styled from 'styled-components'; -import QrCode from './qrCode'; - -export const OuterContainer = styled.div((props) => ({ - ...props.theme.scrollbar, - display: 'flex', - flexDirection: 'column', - flex: 1, - paddingLeft: props.theme.spacing(4), - paddingRight: props.theme.spacing(4), -})); - -const TopTitleText = styled.h1((props) => ({ - ...props.theme.headline_s, - textAlign: 'center', - marginTop: props.theme.spacing(12), -})); - -const DescriptionText = styled.p((props) => ({ - ...props.theme.body_m, - textAlign: 'center', - color: props.theme.colors.white_200, - marginTop: props.theme.spacing(6), -})); - -const AddressContainer = styled.div((props) => ({ - display: 'flex', - flexDirection: 'row', - justifyContent: 'flex-start', - alignItems: 'flex-start', - columnGap: 16, - marginTop: props.theme.spacing(26.5), -})); - -const QRCodeContainer = styled.div((props) => ({ - display: 'flex', - backgroundColor: props.theme.colors.white['0'], - justifyContent: 'center', - alignItems: 'center', - borderRadius: props.theme.radius(1), - width: 159, - height: 159, - alignSelf: 'center', - marginTop: props.theme.spacing(15), -})); - -const AddressText = styled.h1((props) => ({ - ...props.theme.body_m, - color: props.theme.colors.white['200'], - wordBreak: 'break-all', -})); - -const BottomBarContainer = styled.div({ - marginTop: 22, -}); - -const Button = styled.button((props) => ({ - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - marginLeft: props.theme.spacing(3), - padding: 12, - background: props.theme.colors.elevation2, - borderRadius: 24, - width: 40, - height: 40, -})); - -function UpdatedReceive(): JSX.Element { - const { t } = useTranslation('translation', { keyPrefix: 'RECEIVE' }); - const [isCopied, setIsCopied] = useState(false); - const [isBtcReceiveAlertVisible, setIsBtcReceiveAlertVisible] = useState(false); - const [isOrdinalReceiveAlertVisible, setIsOrdinalReceiveAlertVisible] = useState(false); - const navigate = useNavigate(); - const { stxAddress, btcAddress, ordinalsAddress, showOrdinalReceiveAlert } = useWalletSelector(); - - // TODO : Get currency from param - const currency: string = 'ORD'; - - const getAddress = () => { - switch (currency) { - case 'STX': - return stxAddress; - case 'BTC': - return btcAddress; - case 'FT': - return stxAddress; - case 'ORD': - return ordinalsAddress; - default: - return ''; - } - }; - const handleBackButtonClick = () => { - navigate(-1); - }; - - const renderHeading = () => { - if (currency === 'ORD') { - return ( - <> - {t('ORDINAL_ADDRESS')} - {t('ORDINALS_RECEIVE_MESSAGE')} - - ); - } - return ( - <> - {t('STX_ADDRESS')} - {t('STX_RECEIVE_MESSAGE')} - - ); - }; - - const onReceiveAlertClose = () => { - setIsBtcReceiveAlertVisible(false); - }; - - const onOrdinalReceiveAlertClose = () => { - setIsOrdinalReceiveAlertVisible(false); - }; - - const handleOnClick = () => { - setIsCopied(true); - navigator.clipboard.writeText(getAddress()); - if (currency === 'ORD' && showOrdinalReceiveAlert) { - setIsOrdinalReceiveAlertVisible(true); - } - }; - - return ( - <> - - - {renderHeading()} - - - - - {getAddress()} - - - - - - - - {isBtcReceiveAlertVisible && ( - - )} - {isOrdinalReceiveAlertVisible && ( - - )} - - ); -} - -export default UpdatedReceive; diff --git a/src/app/screens/restoreFunds/index.tsx b/src/app/screens/restoreFunds/index.tsx index d08635be9..079bbbc41 100644 --- a/src/app/screens/restoreFunds/index.tsx +++ b/src/app/screens/restoreFunds/index.tsx @@ -30,8 +30,8 @@ function RestoreFunds() { navigate(-1); }; - const handleOnRestoreOridnalClick = () => { - navigate('/recover-ordinals'); + const handleOnRestoreOrdinalClick = () => { + navigate('/restore-ordinals'); }; return ( @@ -43,7 +43,7 @@ function RestoreFunds() { image={OrdinalsIcon} title={t('RECOVER_ORDINALS')} description={t('RECOVER_ORDINALS_DESC')} - onClick={handleOnRestoreOridnalClick} + onClick={handleOnRestoreOrdinalClick} />
diff --git a/src/app/screens/restoreFunds/restoreOrdinals/ordinalRow.tsx b/src/app/screens/restoreFunds/restoreOrdinals/ordinalRow.tsx index 14d0dddc6..372af796d 100644 --- a/src/app/screens/restoreFunds/restoreOrdinals/ordinalRow.tsx +++ b/src/app/screens/restoreFunds/restoreOrdinals/ordinalRow.tsx @@ -67,26 +67,23 @@ interface Props { function OrdinalRow({ ordinal, isLoading, disableTransfer, handleOrdinalTransfer }: Props) { const { t } = useTranslation('translation'); - const inscriptionQuery = useInscriptionDetails(ordinal.id); + const { data: ordinalData, isLoading: isQuerying } = useInscriptionDetails(ordinal.id); - const onClick = async () => { - if (inscriptionQuery && inscriptionQuery.data) { - await handleOrdinalTransfer(ordinal); - } - }; - - if (!inscriptionQuery.isLoading) { + if (!isQuerying && ordinalData) { return ( - + - {`Inscription ${inscriptionQuery.data!.number}`} + {`Inscription ${ordinalData.number}`} Ordinal - + handleOrdinalTransfer(ordinal)} + disabled={disableTransfer} + > {isLoading ? ( diff --git a/src/app/screens/sendBrc20/brc20TransferForm.tsx b/src/app/screens/sendBrc20/brc20TransferForm.tsx index 3e1597b6e..e6525e4ec 100644 --- a/src/app/screens/sendBrc20/brc20TransferForm.tsx +++ b/src/app/screens/sendBrc20/brc20TransferForm.tsx @@ -111,7 +111,7 @@ function Brc20TransferForm(props: Props) { return ( - + {t('AMOUNT')} diff --git a/src/app/screens/sendBrc20/index.tsx b/src/app/screens/sendBrc20/index.tsx index cb03f1ef2..e9774320b 100644 --- a/src/app/screens/sendBrc20/index.tsx +++ b/src/app/screens/sendBrc20/index.tsx @@ -1,6 +1,7 @@ import ActionButton from '@components/button'; import BottomBar from '@components/tabBar'; import TopRow from '@components/topRow'; +import { useGetBrc20FungibleTokens } from '@hooks/queries/ordinals/useGetBrc20FungibleTokens'; import useBtcClient from '@hooks/useBtcClient'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useSeedVault from '@hooks/useSeedVault'; @@ -28,15 +29,15 @@ const BRC20TokenTagContainer = styled.div((props) => ({ })); const BRC20TokenTag = styled.div((props) => ({ - background: props.theme.colors.white[400], + background: props.theme.colors.white_400, borderRadius: 40, width: 54, height: 19, padding: '2px 6px', h1: { - ...props.theme.body_bold_l, + ...props.theme.typography.body_bold_l, fontSize: 11, - color: props.theme.colors.background.elevation0, + color: props.theme.colors.elevation0, }, })); @@ -55,8 +56,9 @@ const SendButtonContainer = styled.div((props) => ({ function SendBrc20Screen() { const { t } = useTranslation('translation'); const navigate = useNavigate(); - const { btcAddress, ordinalsAddress, selectedAccount, network, btcFiatRate, brcCoinsList } = + const { btcAddress, ordinalsAddress, selectedAccount, network, btcFiatRate } = useWalletSelector(); + const { data: brc20CoinsList } = useGetBrc20FungibleTokens(); const { getSeed } = useSeedVault(); const [amountError, setAmountError] = useState(''); const [amountToSend, setAmountToSend] = useState(''); @@ -67,7 +69,7 @@ function SendBrc20Screen() { const coinTicker = location.search ? location.search.split('coinTicker=')[1] : undefined; const fungibleToken = - location.state?.fungibleToken || brcCoinsList?.find((coin) => coin.ticker === coinTicker); + location.state?.fungibleToken || brc20CoinsList?.find((coin) => coin.ticker === coinTicker); const isSendButtonEnabled = amountToSend !== '' && diff --git a/src/app/screens/sendBrc20OneStep/brc20TransferForm.tsx b/src/app/screens/sendBrc20OneStep/brc20TransferForm.tsx index ecf469f3c..4aa7629a1 100644 --- a/src/app/screens/sendBrc20OneStep/brc20TransferForm.tsx +++ b/src/app/screens/sendBrc20OneStep/brc20TransferForm.tsx @@ -153,7 +153,7 @@ function Brc20TransferForm(props: Props) { - + diff --git a/src/app/screens/sendBrc20OneStep/index.tsx b/src/app/screens/sendBrc20OneStep/index.tsx index 850c72cc3..bf0663545 100644 --- a/src/app/screens/sendBrc20OneStep/index.tsx +++ b/src/app/screens/sendBrc20OneStep/index.tsx @@ -1,5 +1,6 @@ import BottomBar from '@components/tabBar'; import TopRow from '@components/topRow'; +import { useGetBrc20FungibleTokens } from '@hooks/queries/ordinals/useGetBrc20FungibleTokens'; import useBtcClient from '@hooks/useBtcClient'; import useBtcFeeRate from '@hooks/useBtcFeeRate'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; @@ -29,7 +30,8 @@ function SendBrc20Screen() { const { t } = useTranslation('translation', { keyPrefix: 'SEND_BRC20' }); const navigate = useNavigate(); const location = useLocation(); - const { btcAddress, ordinalsAddress, network, brcCoinsList } = useWalletSelector(); + const { btcAddress, ordinalsAddress, network } = useWalletSelector(); + const { data: brc20CoinsList } = useGetBrc20FungibleTokens(); const { data: feeRate } = useBtcFeeRate(); const [amountError, setAmountError] = useState(null); const [amountToSend, setAmountToSend] = useState(''); @@ -48,7 +50,7 @@ function SendBrc20Screen() { const { fungibleToken: ft }: SendBrc20TransferState = location.state || {}; const coinName = location.search ? location.search.split('coinName=')[1] : undefined; - const fungibleToken = ft || brcCoinsList?.find((coin) => coin.name === coinName); + const fungibleToken = ft || brc20CoinsList?.find((coin) => coin.name === coinName); const handleBackButtonClick = () => { navigate(-1); diff --git a/src/app/screens/sendBtc/index.tsx b/src/app/screens/sendBtc/index.tsx index 530c6cc5e..cd4c5ed4a 100644 --- a/src/app/screens/sendBtc/index.tsx +++ b/src/app/screens/sendBtc/index.tsx @@ -1,8 +1,9 @@ import useBtcFeeRate from '@hooks/useBtcFeeRate'; import { useResetUserFlow } from '@hooks/useResetUserFlow'; import useTransactionContext from '@hooks/useTransactionContext'; -import { Transport, btcTransaction } from '@secretkeylabs/xverse-core'; -import { isInOptions } from '@utils/helper'; +import useWalletSelector from '@hooks/useWalletSelector'; +import { btcTransaction, Transport } from '@secretkeylabs/xverse-core'; +import { isInOptions, isLedgerAccount } from '@utils/helper'; import { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { @@ -11,7 +12,7 @@ import { type TransactionSummary, } from './helpers'; import StepDisplay from './stepDisplay'; -import { Step, getPreviousStep } from './steps'; +import { getPreviousStep, Step } from './steps'; function SendBtcScreen() { const navigate = useNavigate(); @@ -23,7 +24,7 @@ function SendBtcScreen() { const location = useLocation(); const { data: btcFeeRate, isLoading: feeRatesLoading } = useBtcFeeRate(); - + const { selectedAccount } = useWalletSelector(); const [recipientAddress, setRecipientAddress] = useState(location.state?.recipientAddress || ''); const [isLoading, setIsLoading] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); @@ -53,20 +54,18 @@ function SendBtcScreen() { const generateTransactionAndSummary = async (feeRateOverride?: number) => { const amountBigInt = Number.isNaN(Number(amountSats)) ? 0n : BigInt(amountSats); - const transactionDetails = - sendMax && currentStep !== Step.Confirm - ? await generateSendMaxTransaction( - transactionContext, - recipientAddress, - feeRateOverride ?? +feeRate, - ) - : await generateTransaction( - transactionContext, - recipientAddress, - amountBigInt, - feeRateOverride ?? +feeRate, - ); - return transactionDetails; + return sendMax && currentStep !== Step.Confirm + ? generateSendMaxTransaction( + transactionContext, + recipientAddress, + feeRateOverride ?? +feeRate, + ) + : generateTransaction( + transactionContext, + recipientAddress, + amountBigInt, + feeRateOverride ?? +feeRate, + ); }; useEffect(() => { @@ -105,7 +104,7 @@ function SendBtcScreen() { }, [transactionContext, recipientAddress, amountSats, feeRate, sendMax]); const handleCancel = () => { - if (isInOption) { + if (isLedgerAccount(selectedAccount) && isInOption) { window.close(); return; } diff --git a/src/app/screens/sendBtc/stepDisplay.tsx b/src/app/screens/sendBtc/stepDisplay.tsx index bf7747055..0f344e71d 100644 --- a/src/app/screens/sendBtc/stepDisplay.tsx +++ b/src/app/screens/sendBtc/stepDisplay.tsx @@ -73,7 +73,7 @@ function StepDisplay({ const { t } = useTranslation('translation'); const header = ( - + {t('SEND.SEND')} ); diff --git a/src/app/screens/sendFt/index.tsx b/src/app/screens/sendFt/index.tsx index 94d91cf6d..978ed5c3c 100644 --- a/src/app/screens/sendFt/index.tsx +++ b/src/app/screens/sendFt/index.tsx @@ -1,6 +1,7 @@ import SendForm from '@components/sendForm'; import BottomBar from '@components/tabBar'; import TopRow from '@components/topRow'; +import { useVisibleSip10FungibleTokens } from '@hooks/queries/stx/useGetSip10FungibleTokens'; import useStxPendingTxData from '@hooks/queries/useStxPendingTxData'; import useNetworkSelector from '@hooks/useNetwork'; import useWalletSelector from '@hooks/useWalletSelector'; @@ -21,7 +22,8 @@ import { useLocation, useNavigate } from 'react-router-dom'; function SendFtScreen() { const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); const navigate = useNavigate(); - const { stxAddress, stxPublicKey, network, coinsList, feeMultipliers } = useWalletSelector(); + const { stxAddress, stxPublicKey, network, feeMultipliers } = useWalletSelector(); + const { visible: sip10CoinsList } = useVisibleSip10FungibleTokens(); const [amountError, setAmountError] = useState(''); const [addressError, setAddressError] = useState(''); const [memoError, setMemoError] = useState(''); @@ -34,7 +36,7 @@ function SendFtScreen() { const coinTicker = location.search ? location.search.split('coinTicker=')[1] : undefined; const fungibleToken = - location.state?.fungibleToken || coinsList?.find((coin) => coin.ticker === coinTicker); + location.state?.fungibleToken || sip10CoinsList.find((coin) => coin.ticker === coinTicker); let recipientAddress: string | undefined; let ftAmountToSend: string | undefined; diff --git a/src/app/screens/sendRune/amountSelector.tsx b/src/app/screens/sendRune/amountSelector.tsx new file mode 100644 index 000000000..977b59002 --- /dev/null +++ b/src/app/screens/sendRune/amountSelector.tsx @@ -0,0 +1,126 @@ +import useBtcFeeRate from '@hooks/useBtcFeeRate'; +import useWalletSelector from '@hooks/useWalletSelector'; +import RuneAmountSelector from '@screens/sendRune/runeAmountSelector'; +import { FungibleToken, getBtcFiatEquivalent } from '@secretkeylabs/xverse-core'; +import SelectFeeRate from '@ui-components/selectFeeRate'; +import Button from '@ui-library/button'; +import Callout from '@ui-library/callout'; +import { getFtBalance } from '@utils/tokens'; +import BigNumber from 'bignumber.js'; +import { useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +const Container = styled.div` + width: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +`; + +const FeeRateContainer = styled.div` + margin-top: ${(props) => props.theme.spacing(12)}px; + margin-bottom: ${(props) => props.theme.spacing(8)}px; +`; + +const Buttons = styled.div` + margin: ${(props) => props.theme.spacing(12)}px 0; +`; + +type Props = { + token: FungibleToken; + amountToSend: string; + setAmountToSend: (amount: string) => void; + amountError: string; + feeRate: string; + setFeeRate: (feeRate: string) => void; + sendMax: boolean; + setSendMax: (sendMax: boolean) => void; + fee: string | undefined; + getFeeForFeeRate: (feeRate: number, useEffectiveFeeRate?: boolean) => Promise; + onNext: () => void; + dustFiltered: boolean; + hasSufficientFunds: boolean; + isLoading?: boolean; + header?: React.ReactNode; +}; + +function AmountSelector({ + token, + amountToSend, + setAmountToSend, + amountError, + feeRate, + setFeeRate, + sendMax, + setSendMax, + fee, + getFeeForFeeRate, + onNext, + isLoading, + dustFiltered, + hasSufficientFunds, + header, +}: Props) { + const { t } = useTranslation('translation', { keyPrefix: 'SEND' }); + const { t: tUnits } = useTranslation('translation', { keyPrefix: 'UNITS' }); + const { btcFiatRate, fiatCurrency } = useWalletSelector(); + const { data: recommendedFees } = useBtcFeeRate(); + + const balance = getFtBalance(token); + + const satsToFiat = (sats: string) => + getBtcFiatEquivalent(new BigNumber(sats), BigNumber(btcFiatRate)).toNumber().toFixed(2); + + const isSendButtonEnabled = + amountToSend !== '' && + !Number.isNaN(Number(amountToSend)) && + !Number.isNaN(Number(balance)) && + +amountToSend > 0 && + +amountToSend <= +balance; + + return ( + +
+ {header} + + + + + {sendMax && dustFiltered && } +
+ +