Skip to content

Commit

Permalink
feature: add send from spark to spark address functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
narekpetrosyan committed Dec 24, 2024
1 parent e36e2d3 commit 643b114
Show file tree
Hide file tree
Showing 16 changed files with 4,650 additions and 3,789 deletions.
21 changes: 18 additions & 3 deletions packages/extension/src/libs/spark-handler/callRPC.ts
Original file line number Diff line number Diff line change
@@ -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<T = any>(
method: string,
params?: object
): Promise<T> {
try {
const response = await axios.post(
rpcURL,
const response = await axiosInstance.post(
RPC_URLS['mainnet'],
{
jsonrpc: "1.0",
id: "js-client",
Expand All @@ -21,6 +32,10 @@ export async function callRPC<T = any>(
},
}
);

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);
Expand Down
15 changes: 15 additions & 0 deletions packages/extension/src/libs/spark-handler/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,18 @@ export async function sendToSparkAddress(to: string, amount: string) {
},
]);
}

export async function sendFromSparkAddress(
to: string,
amount: string,
subtractFee = false
): Promise<string> {
return await callRPC<string>("spendspark", [
{
[to]: {
amount: Number(amount),
subtractFee,
},
},
]);
}
6 changes: 3 additions & 3 deletions packages/extension/src/providers/bitcoin/libs/api-firo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {}

async getRawTransaction(hash: string): Promise<string | null> {
Expand Down Expand Up @@ -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<HaskoinUnspentType[]> {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NetworkNames } from "@enkryptcom/types";
import icon from './icons/firo.svg';
import {
BitcoinNetwork,
BitcoinNetworkOptions,
Expand All @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion packages/extension/src/providers/bitcoin/networks/firo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NetworkNames } from "@enkryptcom/types";
import icon from './icons/firo.svg';
import {
BitcoinNetwork,
BitcoinNetworkOptions,
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<template>
<div class="send-address-input" :class="{ focus: isFocus }">
<div class="send-address-input__avatar">
<img
v-if="
isSparkAddress(btcAddress) ||
isAddress(btcAddress, props.network.networkInfo)
"
:src="network.identicon(btcAddress)"
alt=""
/>
</div>
<div class="send-address-input__address">
<p>{{ props.title }}:</p>
<input
ref="addressInput"
v-model="address"
type="text"
:disabled="disableDirectInput"
placeholder="address"
:style="{
color:
!isSparkAddress(btcAddress) &&
!isAddress(btcAddress, props.network.networkInfo)
? 'red'
: 'black',
}"
@focus="changeFocus"
@blur="changeFocus"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { PropType, ref, computed } from "vue";
import { isAddress, isSparkAddress } from "@/providers/bitcoin/libs/utils";
import { BitcoinNetwork } from "@/providers/bitcoin/types/bitcoin-network";
const isFocus = ref<boolean>(false);
const addressInput = ref<HTMLInputElement>();
const pasteFromClipboard = async () => {
try {
const text = await navigator.clipboard.readText();
if (addressInput.value) {
addressInput.value?.focus()
emit("update:inputAddress", text);
}
} catch (err) {
console.error("Failed to read clipboard:", err);
}
};
defineExpose({ addressInput, pasteFromClipboard });
const props = defineProps({
value: {
type: String,
default: () => {
return "";
},
},
network: {
type: Object as PropType<BitcoinNetwork>,
default: () => ({}),
},
title: {
type: String,
default: "To Spark address",
},
disableDirectInput: Boolean,
});
const emit = defineEmits<{
(e: "update:inputAddress", address: string): void;
(e: "toggle:showContacts", show: boolean): void;
}>();
const btcAddress = computed(() => {
return props.value;
});
const address = computed({
get: () => btcAddress.value,
set: (value) => emit("update:inputAddress", value),
});
const changeFocus = (val: FocusEvent) => {
isFocus.value = val.type === "focus";
if (isFocus.value) emit("toggle:showContacts", isFocus.value);
};
</script>

<style lang="less">
@import "@action/styles/theme.less";
.send-address-input {
height: 64px;
background: #ffffff;
margin: 12px 32px 8px 32px;
box-sizing: border-box;
border: 1px solid @gray02;
box-sizing: border-box;
border-radius: 10px;
width: calc(~"100% - 64px");
padding: 16px;
display: flex;
justify-content: flex-start;
align-items: center;
flex-direction: row;
position: relative;
&.focus {
border: 2px solid @primary;
width: calc(~"100% - 62px");
margin: 12px 31px 8px 31px;
}
&__avatar {
background: @buttonBg;
box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.16);
width: 32px;
height: 32px;
border-radius: 100%;
overflow: hidden;
margin-right: 12px;
img {
width: 100%;
height: 100%;
}
}
&__address {
p {
font-style: normal;
font-weight: 400;
font-size: 12px;
line-height: 16px;
letter-spacing: 0.5px;
color: @secondaryLabel;
margin: 0;
}
input {
width: 290px;
height: 24px;
font-style: normal;
font-weight: 400;
font-size: 16px;
line-height: 24px;
letter-spacing: 0.25px;
color: @primaryLabel;
border: 0 none;
outline: none;
padding: 0;
}
}
&__arrow {
position: absolute;
font-size: 0;
cursor: pointer;
padding: 4px;
right: 8px;
top: 16px;
}
}
</style>
Loading

0 comments on commit 643b114

Please sign in to comment.