Skip to content

Commit

Permalink
Add lightning latch functions to nodeJS client
Browse files Browse the repository at this point in the history
  • Loading branch information
ssantos21 committed Jul 16, 2024
1 parent f839817 commit 8ba7bfe
Show file tree
Hide file tree
Showing 7 changed files with 247 additions and 16 deletions.
7 changes: 1 addition & 6 deletions clients/apps/nodejs/config/default.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
{
"statechainEntity": "http://0.0.0.0:8000",
"__statechainEntity": "http://j23wevaeducxuy3zahd6bpn4x76cymwz2j3bdixv7ow4awjrg5p6jaid.onion",
"_statechainEntity": "http://45.76.136.11:8500/",
"__electrumServer": "tcp://0.0.0.0:50001",
"electrumServer": "tcp://0.0.0.0:50001",
"electrumType": "electrs",
"_electrumServer": "tcp://0.0.0.0:50001",
"network": "regtest",
"feeRateTolerance": 5,
"databaseFile": "wallet.db",
"confirmationTarget": 2,
"_torProxy": "socks5h://localhost:9050",
"torProxy": null,
"maxFeeRate": 1
}
}
2 changes: 1 addition & 1 deletion clients/apps/nodejs/config/regtest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
"_torProxy": "socks5h://localhost:9050",
"torProxy": null,
"maxFeeRate": 1
}
}
2 changes: 1 addition & 1 deletion clients/apps/nodejs/config/signet.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@
"_torProxy": "socks5h://localhost:9050",
"torProxy": null,
"maxFeeRate": 1
}
}
42 changes: 39 additions & 3 deletions clients/apps/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ async function main() {
let transferReceiveResult = await mercurynodejslib.transferReceive(clientConfig, wallet_name);
receivedStatechainIds = [...receivedStatechainIds, ...transferReceiveResult.receivedStatechainIds];

if (!transferReceiveResult.isThereBatchLocked) {
if (transferReceiveResult.isThereBatchLocked) {
console.log("Statecoin batch still locked. Waiting until expiration or unlock.");
await sleep(5000);
} else {
Expand All @@ -144,6 +144,44 @@ async function main() {

console.log(JSON.stringify(receivedStatechainIds, null, 2));
});

program.command('payment-hash')
.description('Get a payment hash for lightning latch')
.argument('<wallet_name>', 'name of the wallet')
.argument('<statechain_id>', 'statechain id of the coin')
.action(async (wallet_name, statechain_id) => {

let res = await mercurynodejslib.paymentHash(clientConfig, wallet_name, statechain_id);

console.log(JSON.stringify(res, null, 2));
});

program.command('confirm-pending-invoice')
.description('Confirm a pending invoice for lightning latch')
.argument('<wallet_name>', 'name of the wallet')
.argument('<statechain_id>', 'statechain id of the coin')
.action(async (wallet_name, statechain_id) => {

await mercurynodejslib.confirmPendingInvoice(clientConfig, wallet_name, statechain_id);

let res = {
message: 'Invoice confirmed'
};

console.log(JSON.stringify(res, null, 2));
});

program.command('retrieve-pre-image')
.description('Confirm a pending invoice for lightning latch')
.argument('<wallet_name>', 'name of the wallet')
.argument('<statechain_id>', 'statechain id of the coin')
.argument('<batch-id>', 'transfer batch id')
.action(async (wallet_name, statechain_id, batch_id) => {

let res = await mercurynodejslib.retrievePreImage(clientConfig, wallet_name, statechain_id, batch_id);

console.log(JSON.stringify(res, null, 2));
});

program.parse();

Expand All @@ -152,5 +190,3 @@ async function main() {
(async () => {
await main();
})();


54 changes: 52 additions & 2 deletions clients/libs/nodejs/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const withdraw = require('./withdraw');
const transfer_receive = require('./transfer_receive');
const transfer_send = require('./transfer_send');
const coin_status = require('./coin_status');
const lightningLatch = require('./lightning-latch');

const sqlite3 = require('sqlite3').verbose();

Expand Down Expand Up @@ -186,6 +187,52 @@ const transferReceive = async (clientConfig, walletName) => {
return transferReceiveResult;
}

const paymentHash = async (clientConfig, walletName, statechainId) => {

const db = await getDatabase(clientConfig);

const electrumClient = await getElectrumClient(clientConfig);

await coin_status.updateCoins(clientConfig, electrumClient, db, walletName);

let paymentHash = await lightningLatch.createPreImage(clientConfig, db, walletName, statechainId);

electrumClient.close();
db.close();

return paymentHash;
}

const confirmPendingInvoice = async (clientConfig, walletName, statechainId) => {

const db = await getDatabase(clientConfig);

const electrumClient = await getElectrumClient(clientConfig);

await coin_status.updateCoins(clientConfig, electrumClient, db, walletName);

await lightningLatch.confirmPendingInvoice(clientConfig, db, walletName, statechainId);

electrumClient.close();
db.close();
}

const retrievePreImage = async (clientConfig, walletName, statechainId, batchId) => {

const db = await getDatabase(clientConfig);

const electrumClient = await getElectrumClient(clientConfig);

await coin_status.updateCoins(clientConfig, electrumClient, db, walletName);

let preImage = await lightningLatch.retrievePreImage(clientConfig, db, walletName, statechainId, batchId);

electrumClient.close();
db.close();

return preImage;
}

module.exports = {
createWallet,
newToken,
Expand All @@ -196,5 +243,8 @@ module.exports = {
withdrawCoin,
newTransferAddress,
transferSend,
transferReceive
};
transferReceive,
paymentHash,
confirmPendingInvoice,
retrievePreImage
};
138 changes: 138 additions & 0 deletions clients/libs/nodejs/lightning-latch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
const sqlite_manager = require('./sqlite_manager');
const { v4: uuidv4 } = require('uuid');
const axios = require('axios').default;
const { SocksProxyAgent } = require('socks-proxy-agent');
const { CoinStatus } = require('./coin_enum');

const createPreImage = async (clientConfig, db, walletName, statechainId) => {

const batchId = uuidv4();

let wallet = await sqlite_manager.getWallet(db, walletName);

let coinsWithStatechainId = wallet.coins.filter(c => {
return c.statechain_id === statechainId
});

if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) {
throw new Error(`There is no coin for the statechain id ${statechainId}`);
}

// If the user sends to himself, he will have two coins with same statechain_id
// In this case, we need to find the one with the lowest locktime
// Sort the coins by locktime in ascending order and pick the first one
let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0];

if (coin.status != CoinStatus.CONFIRMED && coin.status != CoinStatus.IN_TRANSFER) {
throw new Error(`Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is ${coin.status}`);
}

if (coin.locktime == null) {
throw new Error("Coin.locktime is null");
}

let paymentHashPayload = {
statechain_id: statechainId,
auth_sig: coin.signed_statechain_id,
batch_id: batchId
};

let paymentHash = await sendPaymentHash(clientConfig, paymentHashPayload);

return {
hash: paymentHash,
batchId: batchId
};
}

const sendPaymentHash = async (clientConfig, paymentHashPayload) => {

const url = `${clientConfig.statechainEntity}/transfer/paymenthash`;
const torProxy = clientConfig.torProxy;

let socksAgent = undefined;

if (torProxy) {
socksAgent = { httpAgent: new SocksProxyAgent(torProxy) };
}

let response = await axios.post(url, paymentHashPayload, socksAgent);

return { hash: response?.data?.hash };
}

const confirmPendingInvoice = async (clientConfig, db, walletName, statechainId) => {

let wallet = await sqlite_manager.getWallet(db, walletName);

let coinsWithStatechainId = wallet.coins.filter(c => {
return c.statechain_id === statechainId
});

if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) {
throw new Error(`There is no coin for the statechain id ${statechainId}`);
}

// If the user sends to himself, he will have two coins with same statechain_id
// In this case, we need to find the one with the lowest locktime
// Sort the coins by locktime in ascending order and pick the first one
let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0];

const transferUnlockRequestPayload = {
statechain_id: statechainId,
auth_sig: coin.signed_statechain_id,
auth_pub_key: null
};

const url = `${clientConfig.statechainEntity}/transfer/unlock`;
const torProxy = clientConfig.torProxy;

let socksAgent = undefined;

if (torProxy) {
socksAgent = { httpAgent: new SocksProxyAgent(torProxy) };
}

// If there is an http error an exception will be thrown
await axios.post(url, transferUnlockRequestPayload, socksAgent);
}

const retrievePreImage = async (clientConfig, db, walletName, statechainId, batchId) => {

let wallet = await sqlite_manager.getWallet(db, walletName);

let coinsWithStatechainId = wallet.coins.filter(c => {
return c.statechain_id === statechainId
});

if (!coinsWithStatechainId || coinsWithStatechainId.length === 0) {
throw new Error(`There is no coin for the statechain id ${statechainId}`);
}

// If the user sends to himself, he will have two coins with same statechain_id
// In this case, we need to find the one with the lowest locktime
// Sort the coins by locktime in ascending order and pick the first one
let coin = coinsWithStatechainId.sort((a, b) => a.locktime - b.locktime)[0];

const transferPreimageRequestPayload = {
statechain_id: statechainId,
auth_sig: coin.signed_statechain_id,
previous_user_auth_key: coin.auth_pubkey,
batch_id: batchId,
};

const url = `${clientConfig.statechainEntity}/transfer/transfer_preimage`;
const torProxy = clientConfig.torProxy;

let socksAgent = undefined;

if (torProxy) {
socksAgent = { httpAgent: new SocksProxyAgent(torProxy) };
}

let response = await axios.post(url, transferPreimageRequestPayload, socksAgent);

return { preimage: response?.data?.preimage };
}

module.exports = { createPreImage, confirmPendingInvoice, retrievePreImage };
18 changes: 15 additions & 3 deletions clients/libs/rust/src/lightning_latch.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@

use crate::{client_config::ClientConfig, sqlite_manager::get_wallet};
use anyhow::{anyhow, Result};
use mercurylib::transfer::sender::{PaymentHashRequestPayload, PaymentHashResponsePayload, TransferPreimageRequestPayload, TransferPreimageResponsePayload};
use mercurylib::{transfer::sender::{PaymentHashRequestPayload, PaymentHashResponsePayload, TransferPreimageRequestPayload, TransferPreimageResponsePayload}, wallet::CoinStatus};
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize)]
pub struct CreatePreImageResponse {
pub pre_image: String,
pub hash: String,
pub batch_id: String,
}

Expand All @@ -30,6 +30,18 @@ pub async fn create_pre_image(

let coin = coin.unwrap();

if coin.amount.is_none() {
return Err(anyhow::anyhow!("coin.amount is None"));
}

if coin.status != CoinStatus::CONFIRMED && coin.status != CoinStatus::IN_TRANSFER {
return Err(anyhow::anyhow!("Coin status must be CONFIRMED or IN_TRANSFER to transfer it. The current status is {}", coin.status));
}

if coin.locktime.is_none() {
return Err(anyhow::anyhow!("coin.locktime is None"));
}

let signed_statechain_id = coin.signed_statechain_id.as_ref().unwrap();

let payment_hash_payload = PaymentHashRequestPayload {
Expand All @@ -56,7 +68,7 @@ pub async fn create_pre_image(
let payment_hash_response_payload: PaymentHashResponsePayload = serde_json::from_str(value.as_str())?;

Ok(CreatePreImageResponse {
pre_image: payment_hash_response_payload.hash,
hash: payment_hash_response_payload.hash,
batch_id,
})
}
Expand Down

0 comments on commit 8ba7bfe

Please sign in to comment.