archethic
Version:
Archethic Javascript SDK
189 lines • 7.27 kB
JavaScript
import { maybeUint8ArrayToHex, uint8ArrayToHex } from "./utils.js";
import absinthe from "./api/absinthe.js";
const senderContext = "SENDER";
export default class TransactionSender {
onSent;
onConfirmation;
onFullConfirmation;
onRequiredConfirmation;
onError;
onTimeout;
confirmationNotifier;
errorNotifier;
absintheSocket;
nbConfirmationReceived;
timeout;
core;
constructor(archethic) {
this.core = archethic;
this.onSent = [];
this.onConfirmation = [];
this.onFullConfirmation = [];
this.onRequiredConfirmation = [];
this.onError = [];
this.onTimeout = [];
this.confirmationNotifier = undefined;
this.errorNotifier = undefined;
this.absintheSocket = undefined;
this.timeout = undefined;
this.nbConfirmationReceived = 0;
return this;
}
on(event, func) {
switch (event) {
case "sent":
this.onSent.push(func);
break;
case "confirmation":
this.onConfirmation.push(func);
break;
case "requiredConfirmation":
this.onRequiredConfirmation.push(func);
break;
case "fullConfirmation":
this.onFullConfirmation.push(func);
break;
case "error":
this.onError.push(func);
break;
case "timeout":
this.onTimeout.push(func);
break;
default:
throw "Event " + event + " is not supported";
}
return this;
}
async send(tx, endpoint, confirmationThreshold = 100, timeout = 60) {
if (confirmationThreshold < 0 && confirmationThreshold > 100) {
throw "'confirmationThreshold' must be an integer between 0 and 100";
}
if (timeout <= 0) {
throw "'timeout' must be an integer greater than 0";
}
const txAddress = uint8ArrayToHex(tx.address);
const { host, protocol } = new URL(endpoint);
const ws_protocol = protocol == "https:" ? "wss" : "ws";
this.absintheSocket = absinthe.create(`${ws_protocol}://${host}/socket`);
try {
this.confirmationNotifier = await waitConfirmations(txAddress, this.absintheSocket, (nbConf, maxConf) => handleConfirmation.call(this, confirmationThreshold, nbConf, maxConf));
this.errorNotifier = await waitError(txAddress, this.absintheSocket, handleError.bind(this));
}
catch (err) {
this.onError.forEach((func) => func(senderContext, err.message, this));
return this;
}
this.core.rpcNode.sendTransaction(tx)
.then(() => {
handleSend.call(this, timeout);
})
.catch((err) => handleError.call(this, senderContext, err));
return this;
}
unsubscribe(event = undefined) {
if (event) {
switch (event) {
case "sent":
this.onSent = [];
break;
case "confirmation":
this.onConfirmation = [];
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
break;
case "requiredConfirmation":
this.onRequiredConfirmation = [];
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
break;
case "fullConfirmation":
this.onFullConfirmation = [];
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
break;
case "error":
this.onError = [];
absinthe.cancel(this.absintheSocket, this.errorNotifier);
break;
case "timeout":
this.onTimeout = [];
break;
default:
throw "Event " + event + " is not supported";
}
}
else {
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
absinthe.cancel(this.absintheSocket, this.errorNotifier);
this.onConfirmation = [];
this.onFullConfirmation = [];
this.onRequiredConfirmation = [];
this.onError = [];
this.onTimeout = [];
this.onSent = [];
}
}
}
async function waitConfirmations(address, absintheSocket, handler) {
const operation = `
subscription {
transactionConfirmed(address: "${maybeUint8ArrayToHex(address)}") {
nbConfirmations,
maxConfirmations
}
}
`;
const notifier = absinthe.send(absintheSocket, operation);
return absinthe.observe(absintheSocket, notifier, (result) => {
if (result.data.transactionConfirmed) {
const { nbConfirmations: nbConfirmations, maxConfirmations: maxConfirmations, } = result.data.transactionConfirmed;
handler(nbConfirmations, maxConfirmations);
}
});
}
async function waitError(address, absintheSocket, handler) {
const operation = `
subscription {
transactionError(address: "${maybeUint8ArrayToHex(address)}") {
context,
reason
}
}
`;
const notifier = absinthe.send(absintheSocket, operation);
return absinthe.observe(absintheSocket, notifier, (result) => {
if (result.data.transactionError) {
const { context: context, reason: reason } = result.data.transactionError;
handler(context, reason);
}
});
}
function handleConfirmation(confirmationThreshold, nbConfirmations, maxConfirmations) {
this.nbConfirmationReceived = nbConfirmations;
if (nbConfirmations == 1)
absinthe.cancel(this.absintheSocket, this.errorNotifier);
this.onConfirmation.forEach((func) => func(nbConfirmations, maxConfirmations, this));
if (maxConfirmations * (confirmationThreshold / 100) <= nbConfirmations &&
this.onRequiredConfirmation.length > 0) {
this.onRequiredConfirmation.forEach((func) => func(nbConfirmations, this));
this.onRequiredConfirmation = [];
clearTimeout(this.timeout);
}
if (nbConfirmations == maxConfirmations) {
clearTimeout(this.timeout);
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
this.onFullConfirmation.forEach((func) => func(maxConfirmations, this));
}
}
function handleError(context, reason) {
clearTimeout(this.timeout);
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
absinthe.cancel(this.absintheSocket, this.errorNotifier);
this.onError.forEach((func) => func(context, reason, this));
}
function handleSend(timeout) {
this.onSent.forEach((func) => func(this));
this.timeout = setTimeout(() => {
absinthe.cancel(this.absintheSocket, this.confirmationNotifier);
absinthe.cancel(this.absintheSocket, this.errorNotifier);
this.onTimeout.forEach((func) => func(this.nbConfirmationReceived, this));
}, timeout * 1_000);
}
//# sourceMappingURL=transaction_sender.js.map