@nimiq/libswap
Version:
Typed Javascript library to handle atomic swaps.
119 lines (118 loc) • 5.27 kB
JavaScript
import { shim as shimPromiseFinally } from 'promise.prototype.finally';
shimPromiseFinally();
export class NimiqAssetAdapter {
constructor(client) {
this.client = client;
this.cancelCallback = null;
this.stopped = false;
}
async findTransaction(address, test) {
return new Promise(async (resolve, reject) => {
const listener = (tx) => {
if (!test(tx))
return false;
cleanup();
resolve(tx);
return true;
};
const transactionListener = await this.client.addTransactionListener(listener, [address]);
let history = [];
const checkHistory = async () => {
history = await this.client.getTransactionsByAddress(address, 0, history);
for (const tx of history) {
if (listener(tx))
break;
}
};
checkHistory();
const consensusListener = await this.client.addConsensusChangedListener((consensusState) => consensusState === 'established' && checkHistory());
const historyCheckInterval = window.setInterval(checkHistory, 60 * 1000);
const cleanup = () => {
this.client.removeListener(transactionListener);
this.client.removeListener(consensusListener);
window.clearInterval(historyCheckInterval);
this.cancelCallback = null;
};
this.cancelCallback = (reason) => {
cleanup();
reject(reason);
};
});
}
async awaitHtlcFunding(address, value, data, confirmations = 0, onPending) {
return this.findTransaction(address, (tx) => {
if (tx.recipient !== address)
return false;
if (tx.value !== value)
return false;
if (typeof tx.data.raw !== 'string' || tx.data.raw !== data)
return false;
if (tx.state === 'included' || tx.state === 'confirmed') {
if (tx.confirmations >= confirmations)
return true;
}
if (typeof onPending === 'function')
onPending(tx);
return false;
});
}
async fundHtlc(serializedTx, onPending, serializedProxyTx) {
if (serializedProxyTx) {
const proxyTx = await this.sendTransaction(serializedProxyTx, false);
const resendInterval = window.setInterval(() => this.sendTransaction(serializedProxyTx, false), 60 * 1000);
await this.findTransaction(proxyTx.recipient, (tx) => tx.transactionHash === proxyTx.transactionHash
&& (tx.state === 'included' || tx.state === 'confirmed')).finally(() => window.clearInterval(resendInterval));
}
const htlcTx = await this.sendTransaction(serializedTx, false);
if (htlcTx.state === 'new' || htlcTx.state === 'pending') {
if (typeof onPending === 'function')
onPending(htlcTx);
const resendInterval = window.setInterval(() => this.sendTransaction(serializedTx, false), 60 * 1000);
return this.awaitHtlcFunding(htlcTx.recipient, htlcTx.value, htlcTx.data.raw)
.finally(() => window.clearInterval(resendInterval));
}
return htlcTx;
}
async awaitHtlcSettlement(address) {
return this.findTransaction(address, (tx) => tx.sender === address
&& typeof tx.proof.preImage === 'string');
}
async awaitSwapSecret(address) {
const tx = await this.awaitHtlcSettlement(address);
return tx.proof.preImage;
}
async settleHtlc(serializedTx, secret, hash) {
const preImageLength = (secret.length / 2).toString(16).padStart(2, '0');
const dummyPreImage = ''.padEnd(secret.length, '0');
serializedTx = serializedTx
.replace(`${hash}${preImageLength}${dummyPreImage}`, `${hash}${preImageLength}${secret}`)
.replace(`66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925${preImageLength}${dummyPreImage}`, `${hash}${preImageLength}${secret}`);
return this.sendTransaction(serializedTx);
}
async awaitSettlementConfirmation(address, onUpdate) {
return this.findTransaction(address, (tx) => {
if (tx.sender !== address)
return false;
if (typeof tx.proof.preImage !== 'string')
return false;
if (tx.state === 'included' || tx.state === 'confirmed')
return true;
if (typeof onUpdate === 'function')
onUpdate(tx);
return false;
});
}
stop(reason) {
if (this.cancelCallback)
this.cancelCallback(reason);
this.stopped = true;
}
async sendTransaction(serializedTx, throwOnFailure = true) {
if (this.stopped)
throw new Error('NimiqAssetAdapter called while stopped');
let tx = await this.client.sendTransaction(serializedTx);
if (throwOnFailure && tx.state === 'new')
throw new Error('Failed to send transaction');
return tx;
}
}