Skip to content

Commit 643b114

Browse files
feature: add send from spark to spark address functionality
1 parent e36e2d3 commit 643b114

16 files changed

Lines changed: 4650 additions & 3789 deletions

File tree

packages/extension/src/libs/spark-handler/callRPC.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import axios from "axios";
22

3-
const rpcURL = "https://firo-rpc.publicnode.com/";
3+
const DEFAULT_TIMEOUT = 30000;
4+
5+
const RPC_URLS = {
6+
mainnet: "https://firo-rpc.publicnode.com/",
7+
};
8+
9+
const axiosInstance = axios.create({
10+
timeout: DEFAULT_TIMEOUT,
11+
headers: {
12+
"Content-Type": "application/json",
13+
}
14+
});
415

516
export async function callRPC<T = any>(
617
method: string,
718
params?: object
819
): Promise<T> {
920
try {
10-
const response = await axios.post(
11-
rpcURL,
21+
const response = await axiosInstance.post(
22+
RPC_URLS['mainnet'],
1223
{
1324
jsonrpc: "1.0",
1425
id: "js-client",
@@ -21,6 +32,10 @@ export async function callRPC<T = any>(
2132
},
2233
}
2334
);
35+
36+
if (!response.data || response.data.result === undefined) {
37+
throw new Error('Invalid RPC response structure');
38+
}
2439
return response.data.result;
2540
} catch (error) {
2641
console.error("RPC Error:", error);

packages/extension/src/libs/spark-handler/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,18 @@ export async function sendToSparkAddress(to: string, amount: string) {
2626
},
2727
]);
2828
}
29+
30+
export async function sendFromSparkAddress(
31+
to: string,
32+
amount: string,
33+
subtractFee = false
34+
): Promise<string> {
35+
return await callRPC<string>("spendspark", [
36+
{
37+
[to]: {
38+
amount: Number(amount),
39+
subtractFee,
40+
},
41+
},
42+
]);
43+
}

packages/extension/src/providers/bitcoin/libs/api-firo.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class API implements ProviderAPIInterface {
2828
return getBitcoinAddress(pubkey, this.networkInfo);
2929
}
3030

31-
// eslint-disable-next-line @typescript-eslint/no-empty-function
31+
3232
async init(): Promise<void> {}
3333

3434
async getRawTransaction(hash: string): Promise<string | null> {
@@ -133,7 +133,7 @@ class API implements ProviderAPIInterface {
133133
ret.sort((a, b) => {
134134
return a.value - b.value;
135135
});
136-
return [ret.at(-1)!]; // TODO: check or filter same values
136+
return ret;
137137
}
138138

139139
async getUTXOs(pubkey: string): Promise<HaskoinUnspentType[]> {
@@ -145,7 +145,7 @@ class API implements ProviderAPIInterface {
145145
return filterOutOrdinals(
146146
address,
147147
this.networkInfo.name,
148-
await this.FiroToHaskoinUTXOs(utxos, address)
148+
[(await this.FiroToHaskoinUTXOs(utxos, address)).at(-1)!]
149149
).then((futxos) => {
150150
futxos.sort((a, b) => {
151151
return a.value - b.value;

packages/extension/src/providers/bitcoin/networks/firo-testnet.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NetworkNames } from "@enkryptcom/types";
2+
import icon from './icons/firo.svg';
23
import {
34
BitcoinNetwork,
45
BitcoinNetworkOptions,
@@ -18,7 +19,7 @@ const firoOptions: BitcoinNetworkOptions = {
1819
isTestNetwork: true,
1920
currencyName: "tFIRO",
2021
currencyNameLong: "tFiro",
21-
icon: require("./icons/firo.svg"),
22+
icon,
2223
decimals: 8,
2324
node: "https://testexplorer.firo.org",
2425
coingeckoID: "zcoin",

packages/extension/src/providers/bitcoin/networks/firo.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { NetworkNames } from "@enkryptcom/types";
2+
import icon from './icons/firo.svg';
23
import {
34
BitcoinNetwork,
45
BitcoinNetworkOptions,
@@ -18,7 +19,7 @@ const firoOptions: BitcoinNetworkOptions = {
1819
isTestNetwork: false,
1920
currencyName: "FIRO",
2021
currencyNameLong: "Firo",
21-
icon: require("./icons/firo.svg"),
22+
icon,
2223
decimals: 8,
2324
node: "https://explorer.firo.org",
2425
coingeckoID: "zcoin",

packages/extension/src/providers/bitcoin/types/bitcoin-network.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ export interface BitcoinNetworkOptions {
5757
}
5858

5959
export const getAddress = (pubkey: string, network: BitcoinNetworkInfo) => {
60-
if (pubkey.length < 64) return pubkey;
60+
if (pubkey.length >= 144 || pubkey.length < 64) {
61+
return pubkey;
62+
}
6163
const { address } = payments[network.paymentType]({
6264
network,
6365
pubkey: hexToBuffer(pubkey),
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<template>
2+
<div class="send-address-input" :class="{ focus: isFocus }">
3+
<div class="send-address-input__avatar">
4+
<img
5+
v-if="
6+
isSparkAddress(btcAddress) ||
7+
isAddress(btcAddress, props.network.networkInfo)
8+
"
9+
:src="network.identicon(btcAddress)"
10+
alt=""
11+
/>
12+
</div>
13+
<div class="send-address-input__address">
14+
<p>{{ props.title }}:</p>
15+
<input
16+
ref="addressInput"
17+
v-model="address"
18+
type="text"
19+
:disabled="disableDirectInput"
20+
placeholder="address"
21+
:style="{
22+
color:
23+
!isSparkAddress(btcAddress) &&
24+
!isAddress(btcAddress, props.network.networkInfo)
25+
? 'red'
26+
: 'black',
27+
}"
28+
@focus="changeFocus"
29+
@blur="changeFocus"
30+
/>
31+
</div>
32+
</div>
33+
</template>
34+
35+
<script setup lang="ts">
36+
import { PropType, ref, computed } from "vue";
37+
import { isAddress, isSparkAddress } from "@/providers/bitcoin/libs/utils";
38+
import { BitcoinNetwork } from "@/providers/bitcoin/types/bitcoin-network";
39+
40+
const isFocus = ref<boolean>(false);
41+
const addressInput = ref<HTMLInputElement>();
42+
43+
const pasteFromClipboard = async () => {
44+
try {
45+
const text = await navigator.clipboard.readText();
46+
if (addressInput.value) {
47+
addressInput.value?.focus()
48+
emit("update:inputAddress", text);
49+
}
50+
} catch (err) {
51+
console.error("Failed to read clipboard:", err);
52+
}
53+
};
54+
defineExpose({ addressInput, pasteFromClipboard });
55+
56+
const props = defineProps({
57+
value: {
58+
type: String,
59+
default: () => {
60+
return "";
61+
},
62+
},
63+
network: {
64+
type: Object as PropType<BitcoinNetwork>,
65+
default: () => ({}),
66+
},
67+
title: {
68+
type: String,
69+
default: "To Spark address",
70+
},
71+
disableDirectInput: Boolean,
72+
});
73+
const emit = defineEmits<{
74+
(e: "update:inputAddress", address: string): void;
75+
(e: "toggle:showContacts", show: boolean): void;
76+
}>();
77+
const btcAddress = computed(() => {
78+
return props.value;
79+
});
80+
const address = computed({
81+
get: () => btcAddress.value,
82+
set: (value) => emit("update:inputAddress", value),
83+
});
84+
85+
const changeFocus = (val: FocusEvent) => {
86+
isFocus.value = val.type === "focus";
87+
if (isFocus.value) emit("toggle:showContacts", isFocus.value);
88+
};
89+
</script>
90+
91+
<style lang="less">
92+
@import "@action/styles/theme.less";
93+
94+
.send-address-input {
95+
height: 64px;
96+
background: #ffffff;
97+
margin: 12px 32px 8px 32px;
98+
box-sizing: border-box;
99+
border: 1px solid @gray02;
100+
box-sizing: border-box;
101+
border-radius: 10px;
102+
width: calc(~"100% - 64px");
103+
padding: 16px;
104+
display: flex;
105+
justify-content: flex-start;
106+
align-items: center;
107+
flex-direction: row;
108+
position: relative;
109+
110+
&.focus {
111+
border: 2px solid @primary;
112+
width: calc(~"100% - 62px");
113+
margin: 12px 31px 8px 31px;
114+
}
115+
116+
&__avatar {
117+
background: @buttonBg;
118+
box-shadow: inset 0px 0px 1px rgba(0, 0, 0, 0.16);
119+
width: 32px;
120+
height: 32px;
121+
border-radius: 100%;
122+
overflow: hidden;
123+
margin-right: 12px;
124+
125+
img {
126+
width: 100%;
127+
height: 100%;
128+
}
129+
}
130+
131+
&__address {
132+
p {
133+
font-style: normal;
134+
font-weight: 400;
135+
font-size: 12px;
136+
line-height: 16px;
137+
letter-spacing: 0.5px;
138+
color: @secondaryLabel;
139+
margin: 0;
140+
}
141+
142+
input {
143+
width: 290px;
144+
height: 24px;
145+
font-style: normal;
146+
font-weight: 400;
147+
font-size: 16px;
148+
line-height: 24px;
149+
letter-spacing: 0.25px;
150+
color: @primaryLabel;
151+
border: 0 none;
152+
outline: none;
153+
padding: 0;
154+
}
155+
}
156+
157+
&__arrow {
158+
position: absolute;
159+
font-size: 0;
160+
cursor: pointer;
161+
padding: 4px;
162+
right: 8px;
163+
top: 16px;
164+
}
165+
}
166+
</style>

0 commit comments

Comments
 (0)