From 643b114b40e704deceb94959999c1f258c45a563 Mon Sep 17 00:00:00 2001 From: narekpetrosyan Date: Tue, 24 Dec 2024 14:15:00 +0400 Subject: [PATCH] feature: add send from spark to spark address functionality --- .../src/libs/spark-handler/callRPC.ts | 21 +- .../extension/src/libs/spark-handler/index.ts | 15 + .../src/providers/bitcoin/libs/api-firo.ts | 6 +- .../bitcoin/networks/firo-testnet.ts | 3 +- .../src/providers/bitcoin/networks/firo.ts | 3 +- .../bitcoin/types/bitcoin-network.ts | 4 +- .../components/send-spark-address-input.vue | 166 + .../bitcoin/ui/send-transaction/index.vue | 566 +- .../send-transaction/tabs/spark-send-tab.vue | 302 + .../tabs/transparent-send-tab.vue | 79 +- packages/extension/src/ui/action/App.vue | 10 +- .../extension/src/ui/action/router/index.ts | 8 + .../index.vue | 348 + .../index.vue | 14 +- .../hw-wallets/src/ledger/bitcoin/configs.ts | 2 +- yarn.lock | 6892 +++++++++-------- 16 files changed, 4650 insertions(+), 3789 deletions(-) create mode 100644 packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue create mode 100644 packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/spark-send-tab.vue create mode 100644 packages/extension/src/ui/action/views/verify-send-from-spark-transaction/index.vue diff --git a/packages/extension/src/libs/spark-handler/callRPC.ts b/packages/extension/src/libs/spark-handler/callRPC.ts index f369aa27f..2901adfb2 100644 --- a/packages/extension/src/libs/spark-handler/callRPC.ts +++ b/packages/extension/src/libs/spark-handler/callRPC.ts @@ -1,14 +1,25 @@ import axios from "axios"; -const rpcURL = "https://firo-rpc.publicnode.com/"; +const DEFAULT_TIMEOUT = 30000; + +const RPC_URLS = { + mainnet: "https://firo-rpc.publicnode.com/", +}; + +const axiosInstance = axios.create({ + timeout: DEFAULT_TIMEOUT, + headers: { + "Content-Type": "application/json", + } +}); export async function callRPC( method: string, params?: object ): Promise { try { - const response = await axios.post( - rpcURL, + const response = await axiosInstance.post( + RPC_URLS['mainnet'], { jsonrpc: "1.0", id: "js-client", @@ -21,6 +32,10 @@ export async function callRPC( }, } ); + + if (!response.data || response.data.result === undefined) { + throw new Error('Invalid RPC response structure'); + } return response.data.result; } catch (error) { console.error("RPC Error:", error); diff --git a/packages/extension/src/libs/spark-handler/index.ts b/packages/extension/src/libs/spark-handler/index.ts index b6eda8da6..686b20a4c 100644 --- a/packages/extension/src/libs/spark-handler/index.ts +++ b/packages/extension/src/libs/spark-handler/index.ts @@ -26,3 +26,18 @@ export async function sendToSparkAddress(to: string, amount: string) { }, ]); } + +export async function sendFromSparkAddress( + to: string, + amount: string, + subtractFee = false +): Promise { + return await callRPC("spendspark", [ + { + [to]: { + amount: Number(amount), + subtractFee, + }, + }, + ]); +} diff --git a/packages/extension/src/providers/bitcoin/libs/api-firo.ts b/packages/extension/src/providers/bitcoin/libs/api-firo.ts index f30d26ca7..f45201aeb 100644 --- a/packages/extension/src/providers/bitcoin/libs/api-firo.ts +++ b/packages/extension/src/providers/bitcoin/libs/api-firo.ts @@ -28,7 +28,7 @@ class API implements ProviderAPIInterface { return getBitcoinAddress(pubkey, this.networkInfo); } - // eslint-disable-next-line @typescript-eslint/no-empty-function + async init(): Promise {} async getRawTransaction(hash: string): Promise { @@ -133,7 +133,7 @@ class API implements ProviderAPIInterface { ret.sort((a, b) => { return a.value - b.value; }); - return [ret.at(-1)!]; // TODO: check or filter same values + return ret; } async getUTXOs(pubkey: string): Promise { @@ -145,7 +145,7 @@ class API implements ProviderAPIInterface { return filterOutOrdinals( address, this.networkInfo.name, - await this.FiroToHaskoinUTXOs(utxos, address) + [(await this.FiroToHaskoinUTXOs(utxos, address)).at(-1)!] ).then((futxos) => { futxos.sort((a, b) => { return a.value - b.value; diff --git a/packages/extension/src/providers/bitcoin/networks/firo-testnet.ts b/packages/extension/src/providers/bitcoin/networks/firo-testnet.ts index d4907f402..b1bee81de 100644 --- a/packages/extension/src/providers/bitcoin/networks/firo-testnet.ts +++ b/packages/extension/src/providers/bitcoin/networks/firo-testnet.ts @@ -1,4 +1,5 @@ import { NetworkNames } from "@enkryptcom/types"; +import icon from './icons/firo.svg'; import { BitcoinNetwork, BitcoinNetworkOptions, @@ -18,7 +19,7 @@ const firoOptions: BitcoinNetworkOptions = { isTestNetwork: true, currencyName: "tFIRO", currencyNameLong: "tFiro", - icon: require("./icons/firo.svg"), + icon, decimals: 8, node: "https://testexplorer.firo.org", coingeckoID: "zcoin", diff --git a/packages/extension/src/providers/bitcoin/networks/firo.ts b/packages/extension/src/providers/bitcoin/networks/firo.ts index a450cc014..5c36a1f59 100644 --- a/packages/extension/src/providers/bitcoin/networks/firo.ts +++ b/packages/extension/src/providers/bitcoin/networks/firo.ts @@ -1,4 +1,5 @@ import { NetworkNames } from "@enkryptcom/types"; +import icon from './icons/firo.svg'; import { BitcoinNetwork, BitcoinNetworkOptions, @@ -18,7 +19,7 @@ const firoOptions: BitcoinNetworkOptions = { isTestNetwork: false, currencyName: "FIRO", currencyNameLong: "Firo", - icon: require("./icons/firo.svg"), + icon, decimals: 8, node: "https://explorer.firo.org", coingeckoID: "zcoin", diff --git a/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts b/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts index ddac2f801..af4157692 100644 --- a/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts +++ b/packages/extension/src/providers/bitcoin/types/bitcoin-network.ts @@ -57,7 +57,9 @@ export interface BitcoinNetworkOptions { } export const getAddress = (pubkey: string, network: BitcoinNetworkInfo) => { - if (pubkey.length < 64) return pubkey; + if (pubkey.length >= 144 || pubkey.length < 64) { + return pubkey; + } const { address } = payments[network.paymentType]({ network, pubkey: hexToBuffer(pubkey), diff --git a/packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue b/packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue new file mode 100644 index 000000000..47626c946 --- /dev/null +++ b/packages/extension/src/providers/bitcoin/ui/send-transaction/components/send-spark-address-input.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue b/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue index 66138e361..c9a35d3d8 100644 --- a/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue +++ b/packages/extension/src/providers/bitcoin/ui/send-transaction/index.vue @@ -8,166 +8,57 @@ @toggle-type="toggleSelector" /> - +
+ + +
- - - - - - - - - - - - - - - - - - -
-
- -
-
- -
-
+ + diff --git a/packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/transparent-send-tab.vue b/packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/transparent-send-tab.vue index 41cef2344..b93d4457a 100644 --- a/packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/transparent-send-tab.vue +++ b/packages/extension/src/providers/bitcoin/ui/send-transaction/tabs/transparent-send-tab.vue @@ -23,33 +23,27 @@ />
-
- - -
- + - - -
@@ -221,7 +210,6 @@ const loadingAsset = new BTCToken({ }); const addressInputTo = ref(); -const sparkAddressInput = ref(); const isSendSpark = ref(false); const selected: string = route.params.id as string; const paramNFTData: NFTItem = JSON.parse( @@ -321,17 +309,12 @@ const sendButtonTitle = computed(() => { }); const isInputsValid = computed(() => { - if (isSendSpark.value) { - if (!isSparkAddress(sparkAddressInput.value)) { - return false; - } - } else { - if ( - !isAddress(addressTo.value, (props.network as BitcoinNetwork).networkInfo) - ) - return false; + if ( + !isSparkAddress(addressTo.value) && + !isAddress(addressTo.value, (props.network as BitcoinNetwork).networkInfo) + ) { + return false; } - if ( props.isSendToken && !isValidDecimals(sendAmount.value, selectedAsset.value.decimals!) @@ -411,10 +394,6 @@ const inputAddressTo = (text: string) => { addressTo.value = text; }; -const inputSparkAddressTo = (address: string) => { - sparkAddressInput.value = address; -}; - const toggleSelectContactFrom = (open: boolean) => { isOpenSelectContactFrom.value = open; }; @@ -447,10 +426,6 @@ const toggleSelectFee = () => { isOpenSelectFee.value = !isOpenSelectFee.value; }; -const toggleIsSendSpark = (value: boolean) => { - isSendSpark.value = value; -}; - const selectFee = (type: GasPriceTypes) => { selectedFee.value = type; isOpenSelectFee.value = false; @@ -469,8 +444,8 @@ const selectNFT = (item: NFTItemWithCollectionName) => { const sendSparkAction = async () => { const keyring = new PublicKeyRing(); const fromAccountInfo = await keyring.getAccount(addressFrom.value); - const toAmount = toBN(toBase(sendAmount.value, selectedAsset.value.decimals)); + router.push({ name: RouterNames.verifySendToSpark.name, query: { @@ -492,7 +467,7 @@ const sendSparkAction = async () => { fromAddressName: fromAccountInfo.name, gasFee: gasCostValues.value[selectedFee.value], gasPriceType: selectedFee.value, - toAddress: sparkAddressInput.value, + toAddress: addressTo.value, }), "utf8" ).toString("base64"), @@ -514,7 +489,7 @@ const sendAction = async () => { if (props.isSendToken) { txInfo.outputs.push({ - address: isSendSpark.value ? sparkAddressInput.value : addressTo.value, + address: addressTo.value, value: toAmount.toNumber(), }); } else { @@ -600,8 +575,8 @@ const sendAction = async () => { diff --git a/packages/extension/src/ui/action/views/verify-send-to-spark-transaction/index.vue b/packages/extension/src/ui/action/views/verify-send-to-spark-transaction/index.vue index b6a308333..0aaafb4cb 100644 --- a/packages/extension/src/ui/action/views/verify-send-to-spark-transaction/index.vue +++ b/packages/extension/src/ui/action/views/verify-send-to-spark-transaction/index.vue @@ -27,7 +27,7 @@ :network="network" /> @@ -95,6 +95,7 @@ import { sendToSparkAddress } from "@/libs/spark-handler"; import { isAxiosError } from "axios"; import { fromBase } from "@enkryptcom/utils"; import { BaseNetwork } from "@/types/base-network"; +import { isAddress } from "@/providers/bitcoin/libs/utils"; const emits = defineEmits<{ (e: "update:spark-state-changed", network: BaseNetwork): void; @@ -122,9 +123,12 @@ defineExpose({ verifyScrollRef }); onBeforeMount(async () => { network.value = (await getNetworkByName(selectedNetwork)!) as BitcoinNetwork; trackSendEvents(SendEventType.SendVerify, { network: network.value.name }); - account.value = await KeyRing.getAccount(txData.fromAddress); - isWindowPopup.value = account.value.isHardware; + if (isAddress(txData.fromAddress, network.value.networkInfo)) { + account.value = await KeyRing.getAccount(txData.fromAddress); + isWindowPopup.value = account.value.isHardware; + } }); + const close = () => { if (getCurrentContext() === "popup") { router.go(-1); @@ -212,8 +216,8 @@ const isHasScroll = () => {