diff --git a/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue b/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue
index 4ffa2e0..e8368f4 100644
--- a/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue
+++ b/packages/docs/src/.vitepress/theme/components/PrimitiveTool.vue
@@ -17,7 +17,7 @@
v-if="state.selectedFromFunction?.extendedInput"
v-model="state.selectedFromFunction.models[i]"
class="extended-input"
- @input="autoGrow($event.target)"
+ @input="(e) => autoGrow(e.target as HTMLTextAreaElement)"
>
{{ state.toFunctions.length ? 'Output' : 'Instance' }}
-
+
@@ -51,37 +51,51 @@
-
-
+
-
+
@@ -134,6 +148,9 @@ export interface Method
{
// Error (if one occurred).
error?: string;
+ // Whether the number of inputs is dynamic.
+ dynamicInputs?: boolean;
+
// Function that takes the models and returns the primitive
callback: (primitive: Primitive, value: Array) => string | Promise;
@@ -191,7 +208,7 @@ async function setPrimitive(functionToExecute: () => Primitive | Promise {
- state.value.toFunctions[i].result = await wrappedMethod.callback(state.value.primitive)
+ state.value.toFunctions[i].result = await wrappedMethod.callback(state.value.primitive!)
});
state.value.methods.forEach((method, i) => {
@@ -218,14 +235,20 @@ function getPrimitive(functionToExecute: () => string): string {
}
}
-async function executeMethod(wrappedMethod: () => string | Promise): Promise {
+async function executeMethod(wrappedMethod: () => string | Promise | undefined): Promise {
+ if(!wrappedMethod || !state.value.selectedMethod) {
+ return;
+ }
+
try {
if(!state.value.primitive) {
return '';
}
- state.value.selectedMethod.result = await wrappedMethod;
+ const result = await wrappedMethod();
+ // Update the result directly on the selected method
+ state.value.selectedMethod.result = result;
state.value.selectedMethod.error = undefined;
} catch(error: any) {
state.value.selectedMethod.error = error.message;
@@ -234,10 +257,29 @@ async function executeMethod(wrappedMethod: () => string | Promise): Pro
}
};
+async function addMethodInput() {
+ if(!state.value.selectedMethod) {
+ return;
+ }
+
+ state.value.selectedMethod.models.push('');
+ state.value.selectedMethod.inputs.push('');
+}
+
+async function removeMethodInput(index: number) {
+ if(!state.value.selectedMethod) {
+ return;
+ }
+
+ state.value.selectedMethod.models.splice(index, 1);
+ state.value.selectedMethod.inputs.splice(index, 1);
+}
+
// Select the first "from" function.
state.value.selectedFromFunction = state.value.fromFunctions[0];
-const autoGrow = (element: HTMLTextAreaElement) => {
+const autoGrow = (element: HTMLTextAreaElement | null) => {
+ if (!element) return;
element.style.height = 'auto'
element.style.height = `${Math.min(element.scrollHeight, 300)}px`
}
diff --git a/packages/docs/src/.vitepress/theme/tools.css b/packages/docs/src/.vitepress/theme/tools.css
index f9ecff1..9210cac 100644
--- a/packages/docs/src/.vitepress/theme/tools.css
+++ b/packages/docs/src/.vitepress/theme/tools.css
@@ -49,7 +49,7 @@
.form-group select,
.form-group input,
-.form-group button {
+.form-group button:not(.remove-input-button) {
width: 100%; /* Makes fields responsive */
padding: 10px;
font-size: 1rem;
@@ -133,6 +133,44 @@
background: var(--primary);
}
+.add-input-container >button.flat-rounded-button {
+ border-radius: 100px;
+ cursor: pointer;
+ text-transform: uppercase;
+ border: none;
+ padding: 10px 20px;
+ width: auto;
+ font-size: 0.85rem;
+ font-weight: bold;
+ background: transparent;
+}
+
+.add-input-container > button.flat-rounded-button:hover {
+ background: var(--vp-c-divider); /* Super light grey */
+}
+
+.add-input-container {
+ display: flex;
+ justify-content: center;
+ padding-top: 10px;
+}
+
+.input-with-remove {
+ position: relative;
+ display: flex;
+ align-items: center;
+}
+
+.remove-input-button {
+ position: absolute;
+ right: 8px;
+ font-size: 20px;
+}
+
+.remove-input-button:hover {
+ color: #f46666;
+}
+
/* Mobile responsiveness */
@media (max-width: 768px) {
.select-group {
diff --git a/packages/docs/src/tools/stack/blockchain-functions.ts b/packages/docs/src/tools/stack/blockchain-functions.ts
index 589bd77..a72363e 100644
--- a/packages/docs/src/tools/stack/blockchain-functions.ts
+++ b/packages/docs/src/tools/stack/blockchain-functions.ts
@@ -3,6 +3,7 @@ import {
encodeExtendedJson,
Transaction,
} from '@xocash/stack';
+import { ref } from 'vue';
export const electrumClient = {
from: [
@@ -19,6 +20,7 @@ export const electrumClient = {
application: 'XO-tools',
},
});
+ await electrum.start();
return electrum;
},
@@ -26,10 +28,32 @@ export const electrumClient = {
],
to: [],
methods: [
+ {
+ label: 'Request',
+ inputs: ['Method', 'Parameters'],
+ dynamicInputs: true,
+ models: ['', ''],
+ callback: async (
+ electrum: BlockchainElectrum,
+ args: [string, string],
+ ) => {
+ const [method, ...parameters] = args;
+
+ // If the first parameter is empty, don't include it in the request
+ // This parameter can not be removed in the UI so we handle a blank parameter gracefully
+ // Since other parameters can be removed, we assume that blank parameters are intentional and so we include them as part of the request
+ if (!parameters[0] && parameters.length === 1) {
+ parameters.shift();
+ }
+
+ const response = await electrum.request(method, ...parameters);
+ return encodeExtendedJson(response, 2);
+ },
+ },
{
label: 'Fetch Unspents',
inputs: ['Address'],
- models: ['Address'],
+ models: [''],
callback: async (electrum: BlockchainElectrum, args: [string]) => {
const unspents = await electrum.fetchUnspents(args[0]);
return encodeExtendedJson(unspents.toArray(), 2);
@@ -38,7 +62,7 @@ export const electrumClient = {
{
label: 'Fetch Transaction',
inputs: ['Transaction Hash'],
- models: ['Transaction Hash'],
+ models: [''],
callback: async (electrum: BlockchainElectrum, args: [string]) => {
const transaction = await electrum.fetchTransaction(args[0]);
return encodeExtendedJson(transaction, 2);
@@ -47,7 +71,7 @@ export const electrumClient = {
{
label: 'Fetch Address History',
inputs: ['Address'],
- models: ['Address'],
+ models: [''],
callback: async (electrum: BlockchainElectrum, args: [string]) => {
const transactions = await electrum.fetchAddressHistory(args[0]);
return encodeExtendedJson(transactions.toArray(), 2);
@@ -56,11 +80,50 @@ export const electrumClient = {
{
label: 'Broadcast Transaction',
inputs: ['Transaction'],
- models: ['Transaction'],
+ models: [''],
callback: async (electrum: BlockchainElectrum, args: [string]) => {
const tx = Transaction.from(args[0]);
return await electrum.broadcastTransaction(tx);
},
},
+ {
+ label: 'Subscribe Address',
+ inputs: ['Address'],
+ models: [''],
+ callback: async (electrum: BlockchainElectrum, args: [string]) => {
+ // Use a vue ref so we can update the UI dynamically.
+ const addressEvents = ref('');
+
+ await electrum.subscribeAddress(args[0], (status) => {
+ // Don't update if its null
+ if (!status) {
+ return;
+ }
+
+ addressEvents.value += `${status}\n`;
+ });
+
+ return addressEvents;
+ },
+ },
+ {
+ label: 'Subscribe Transaction',
+ inputs: ['Transaction Hash'],
+ models: [''],
+ callback: async (electrum: BlockchainElectrum, args: [string]) => {
+ const transactionEvents = ref('');
+
+ await electrum.subscribeTransaction(args[0], (transaction) => {
+ // Don't update if its null
+ if (!transaction) {
+ return;
+ }
+
+ transactionEvents.value += `${transaction}\n`;
+ });
+
+ return transactionEvents;
+ },
+ },
],
};
diff --git a/packages/docs/src/tools/stack/blockchain.md b/packages/docs/src/tools/stack/blockchain.md
index 68f02ad..8315fa4 100644
--- a/packages/docs/src/tools/stack/blockchain.md
+++ b/packages/docs/src/tools/stack/blockchain.md
@@ -27,4 +27,5 @@ import {
:toFunctions="electrumClient.to"
:methods="electrumClient.methods"
/>
-
\ No newline at end of file
+
+