I'm attempting to dynamically set properties for an HTTP response using the getL function, but it gives me back a Promise and I cannot set async over the forEach, since that breaks it (see the generateLinksForList function). Is there another way to do this?
/**
* #module domain/helper
*/
const {
complement,
compose,
isNil,
pickBy
} = require('ramda');
const notNull = compose(complement(isNil));
/**
* #function cleanData
* #returns {undefined}
*/
const cleanData = (entity) => pickBy(notNull, entity);
/**
* #name generateRelations
* #returns {undefined}
*/
const generateRelations = () => ([]);
async function getL(repo, entityContext) {
let d = await repo.nextItem(entityContext);
console.log(d)
return d;
}
/**
* #name generateLinksForList
* #returns {undefined}
*/
const generateLinksForList = (entityContext, type, entityName, repo) => {
const relations = require(`./${entityName}/relations.js`)
const namespace = type + 'Relations';
const _relations = relations[namespace];
let host = 'http://localhost:4000';
let relationsList = [];
_relations.forEach((linkRelation) => {
Object.keys(linkRelation).forEach((key) => {
if (linkRelation.hasOwnProperty('next')) {
let relation = {};
let l = getL(repo, entityContext);
console.log('---- getL');
console.log(l);
if (l.length) {
relation.next = linkRelation.next
.replace('{nextId}', l[0].id)
}
relation.next = linkRelation.next
.replace('{fullhost}', host)
.replace('{id}', entityContext.id);
relationsList.push(relation);
}
if (linkRelation.hasOwnProperty('self')) {
let relation = {};
relation.self = linkRelation['self']
.replace('{fullhost}', host)
.replace('{id}', entityContext.id);
relationsList.push(relation);
}
})
});
return relationsList;
};
/**
* #function generateLinksForItem
* #static
* #returns {array}
*/
const generateLinksForItem = (entityContext, type, entityName) => {
const relations = require(`./${entityName}/relations.js`)
const namespace = type + 'Relations';
const _relations = relations[namespace];
let host = 'http://localhost:4000';
let token, eventToken;
let _list = [];
_relations.forEach((relRef) => {
Object.keys(relRef).forEach((keyRef) => {
if (relRef[keyRef].includes('{id}')) {
relRef[keyRef] = relRef[keyRef].replace(/{id}/, entityContext.id);
}
if (relRef[keyRef].includes('{fullhost}')) {
relRef[keyRef] = relRef[keyRef].replace(/{fullhost}/, host);
}
if (relRef[keyRef].includes('{token}')) {
relRef[keyRef] = relRef[keyRef].replace(/{token}/, token);
}
if (relRef[keyRef].includes('{eventToken}')) {
relRef[keyRef] = relRef[keyRef].replace(/{eventToken}/, eventToken);
}
_list.push({
rel: keyRef,
href: relRef[keyRef]
});
});
});
return _list;
};
/**
* #function generateClassList
* #static
* #returns {array}
*/
const generateClassList = (context) => (['organization']);
/**
* #function generateEntities
* #param {object} repo Repostory for generating entities under a resource.
* #returns {array}
*/
const generateEntities = (repo) => {
return repo.getAll()
.then((documentSnapshots) => {
return documentSnapshots.map((doc) => {
if (doc.exists) {
let data = doc.data()
return {
//class: generateClassList(data.id),
//rel: generateRelations(data.id),
properties: data
//links: generateLinksForItem(data.id, 'item', 'user')
};
}
});
});
};
/**
* #function generateActions
* #static
* #returns {array}
* #description
* Ok, so here we're basically scripting with CasperJS. The presumption is
* that there will be a generic client through which we can automate browser
* behaviors and platform capabilities (Cordova). We can think of hypermedia
* controls as projections from rhetorical relations over a domain knowledge
* graph that represents the plurality of connections or links in hypermedia
* ensembles (differentiations of numerical and other kinds of identity).
*/
const generateActions = (_itemForms, entity, entityName) => {
let host = `http://localhost:4000/api/${entityName}`;
_itemForms.forEach(function (itemRef, key) {
Object.keys(itemRef).forEach(function (keyRef) {
if (itemRef[keyRef].includes('{id}')) {
itemRef[keyRef] = itemRef[keyRef].replace(/{id}/, entity.id);
}
if (itemRef[keyRef].includes('{fullhost}')) {
itemRef[keyRef] = itemRef[keyRef].replace(/{fullhost}/, host);
}
if (itemRef.hasOwnProperty('properties')) {
itemRef.properties.forEach((p) => {
Object.keys(p).forEach((k) => {
if (p[k].includes('{status}')) {
p[k] = p[k].replace(/{status}/, 'pending');
}
});
});
}
});
});
return _itemForms;
};
module.exports = {
cleanData,
generateLinksForItem,
generateLinksForList,
generateClassList,
generateActions,
generateEntities
};
// EOF
This is where I'm using the generateLinksForList function, hence why I cannot use async for the containing map:
/**
* #module app/place/get
* #description
* Get all places.
*/
const { itemForms } = require('../../domain/place/transitions');
const { Place } = require('../../domain/place');
const {
generateActions,
generateLinksForList,
} = require('../../domain/helper.js');
module.exports = ({ placeRepository }) => {
const all = () => {
return Promise
.resolve()
.then(() => placeRepository.getAll()
.then((documentSnapshots) => {
return documentSnapshots.map((doc) => {
let properties = doc.data() || {};
let place = Place(properties);
let links = generateLinksForList(place, 'item', 'place', placeRepository);
let actions = generateActions(itemForms, doc, 'places');
let props = Object.assign({}, place, {});
let hypermediaResponse = {
actions: actions,
links: links,
properties: props,
class: ['place'],
entities: []
};
if (doc.exists) {
return hypermediaResponse;
}
});
})
)
.catch(error => {
throw new Error(error);
})
}
return {
all
};
};
// EOF
The repo function:
/**
* #module infrastructure/repositories/place/index
* #description
* Repo/methods for places.
*/
const { toEntity } = require('./transform');
module.exports = ({ model, database }) => {
/**
* #name getAll
* #param {Object} args - Firestore clause (where, etc.);
* #returns {Object}
*/
const _getAll = async (...args) =>
await model
.listDocuments().then((docRefs) => database.firestore.getAll(...docRefs));
/**
* #name getAll
* #returns {Promise}
*/
const getAll = async () =>
await model.listDocuments()
.then((docRefs) => database.firestore.getAll(...docRefs));
/**
* #name prev
* #returns {undefined}
*/
const prev = async (...args) => {
let payload = args[0];
let docRef = model.doc(payload.id);
let snapshot = await docRef.get();
let last = snapshot.docs[snapshot.docs.length - 1];
return last.data();
};
/**
* #name next
* #returns {undefined}
*/
const nextItem = async (...args) => {
let payload = args[0];
const docRef = model.doc(payload.id);
const snapshot = (await docRef.get()).data();
const start = await model
.orderBy('createdAt')
.endBefore(snapshot.createdAt)
.limitToLast(1)
//.startAt(snapshot)
//.offset(1)
//.limit(1)
.get();
let list = [];
start.forEach((d) => {
list.push(d.data());
});
return list;
};
/**
* #name create
* #returns {object}
*/
const create = async (...args) => {
let payload = args[0];
let docRef = await model.doc(payload.id);
await docRef.set(payload);
return docRef.get().then((documentSnapshot) => {
return documentSnapshot.data();
})
};
/**
* #name update
* #returns {object}
*/
const update = async (...args) =>
await model.doc(args.id).update(...args);
/**
* #name findById
* #returns {object}
*/
const findById = async (...args) =>
await model.where('id', '==', args.id).get();
/**
* #name findOne
* #returns {object}
*/
const findOne = async (...args) => await model
.limit(1)
.get();
/**
* #name destroy
* #returns {object}
*/
const destroy = async (...args) =>
await model.doc(args.id).delete();
return {
getAll,
create,
update,
findById,
findOne,
destroy,
nextItem,
prev
};
};
// EOF
.forEach won't wait for an async function. Use a normal loop:
for ( const linkRelation of relations ) {
for ( const key in linkRelation) {
...
const l = await getL(repo, entityContext);
...
}
}
Related
I'm currently working with the cardano serialization library to code crypto transactions in typescript. I'm referencing a wallet connector template that has example code for transactions but is all written in Javascript. So in translating it over I've received a number of syntax errors that have generally been easy to resolve until now. I have a number of ts(2532) errors (Object is possibly 'undefined' that are solved through '?' chaining, I know that's not my best solution but for now I'm trying to get it error free first. When I utilize a ? operator chain though I then get the property does not exist on type 'never' error in the following code
getNetworkId = async () => {
try {
const networkId = await this?.API?.getNetworkId()
this.setState({ networkId })
} catch (err) {
console.log(err)
} }
Below I've included the majority of the code down until that error
export default class App extends React.Component<any, any> {
API: undefined
protocolParams: {
linearFee: { minFeeA: string; minFeeB: string }
minUtxo: string
poolDeposit: string
keyDeposit: string
maxValSize: number
maxTxSize: number
priceMem: number
priceStep: number
coinsPerUtxoWord: string
}
constructor(props: any) {
super(props)
this.state = {
selectedTabId: '1',
whichWalletSelected: undefined,
walletFound: false,
walletIsEnabled: false,
walletName: undefined,
walletIcon: undefined,
walletAPIVersion: undefined,
wallets: [],
networkId: undefined,
Utxos: undefined,
CollatUtxos: undefined,
balance: undefined,
changeAddress: undefined,
rewardAddress: undefined,
usedAddress: undefined,
txBody: undefined,
txBodyCborHex_unsigned: '',
txBodyCborHex_signed: '',
submittedTxHash: '',
addressBech32SendADA:
'addr_test1qrt7j04dtk4hfjq036r2nfewt59q8zpa69ax88utyr6es2ar72l7vd6evxct69wcje5cs25ze4qeshejy828h30zkydsu4yrmm',
lovelaceToSend: 3000000,
assetNameHex: '4c494645',
assetPolicyIdHex:
'ae02017105527c6c0c9840397a39cc5ca39fabe5b9998ba70fda5f2f',
assetAmountToSend: 5,
addressScriptBech32:
'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8',
datumStr: '12345678',
plutusScriptCborHex: '4e4d01000033222220051200120011',
transactionIdLocked: '',
transactionIndxLocked: 0,
lovelaceLocked: 3000000,
manualFee: 900000,
}
/**
* When the wallet is connect it returns the connector which is
* written to this API variable and all the other operations
* run using this API object
*/
this.API = undefined
/**
* Protocol parameters
* #type {{
* keyDeposit: string,
* coinsPerUtxoWord: string,
* minUtxo: string,
* poolDeposit: string,
* maxTxSize: number,
* priceMem: number,
* maxValSize: number,
* linearFee: {minFeeB: string, minFeeA: string}, priceStep: number
* }}
*/
this.protocolParams = {
linearFee: {
minFeeA: '44',
minFeeB: '155381',
},
minUtxo: '34482',
poolDeposit: '500000000',
keyDeposit: '2000000',
maxValSize: 5000,
maxTxSize: 16384,
priceMem: 0.0577,
priceStep: 0.0000721,
coinsPerUtxoWord: '34482',
}
this.pollWallets = this.pollWallets.bind(this)
}
/**
* Poll the wallets it can read from the browser.
* Sometimes the html document loads before the browser initialized browser plugins (like Nami or Flint).
* So we try to poll the wallets 3 times (with 1 second in between each try).
*
* Note: CCVault and Eternl are the same wallet, Eternl is a rebrand of CCVault
* So both of these wallets as the Eternl injects itself twice to maintain
* backward compatibility
*
* #param count The current try count.
*/
pollWallets = (count = 0) => {
const wallets = []
for (const key in window.cardano) {
if (window.cardano[key].enable && wallets.indexOf(key) === -1) {
wallets.push(key)
}
}
if (wallets.length === 0 && count < 3) {
setTimeout(() => {
this.pollWallets(count + 1)
}, 1000)
return
}
this.setState(
{
wallets,
whichWalletSelected: wallets[0],
},
() => {
this.refreshData()
}
)
}
/**
* Handles the tab selection on the user form
* #param tabId
*/
handleTabId = (tabId: any) => this.setState({ selectedTabId: tabId })
/**
* Handles the radio buttons on the form that
* let the user choose which wallet to work with
* #param obj
*/
handleWalletSelect = (obj: { target: { value: any } }) => {
const whichWalletSelected = obj.target.value
this.setState({ whichWalletSelected }, () => {
this.refreshData()
})
}
/**
* Generate address from the plutus contract cborhex
*/
generateScriptAddress = () => {
// cborhex of the alwayssucceeds.plutus
// const cborhex = "4e4d01000033222220051200120011";
// const cbor = Buffer.from(cborhex, "hex");
// const blake2bhash = blake.blake2b(cbor, 0, 28);
const script = PlutusScript.from_bytes(
Buffer.from(this.state.plutusScriptCborHex, 'hex')
)
// const blake2bhash = blake.blake2b(script.to_bytes(), 0, 28);
const blake2bhash =
'67f33146617a5e61936081db3b2117cbf59bd2123748f58ac9678656'
const scripthash = ScriptHash.from_bytes(Buffer.from(blake2bhash, 'hex'))
const cred = StakeCredential.from_scripthash(scripthash)
const networkId = NetworkInfo.testnet().network_id()
const baseAddr = EnterpriseAddress.new(networkId, cred)
const addr = baseAddr.to_address()
const addrBech32 = addr.to_bech32()
// hash of the address generated from script
console.log(Buffer.from(addr.to_bytes(), 'utf8').toString('hex'))
// hash of the address generated using cardano-cli
const ScriptAddress = Address.from_bech32(
'addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8'
)
console.log(Buffer.from(ScriptAddress.to_bytes(), 'utf8').toString('hex'))
console.log(ScriptAddress.to_bech32())
console.log(addrBech32)
}
/**
* Checks if the wallet is running in the browser
* Does this for Nami, Eternl and Flint wallets
* #returns {boolean}
*/
checkIfWalletFound = () => {
const walletKey = this.state.whichWalletSelected
const walletFound = !!window?.cardano?.[walletKey]
this.setState({ walletFound })
return walletFound
}
/**
* Checks if a connection has been established with
* the wallet
* #returns {Promise<boolean>}
*/
checkIfWalletEnabled = async () => {
let walletIsEnabled = false
try {
const walletName = this.state.whichWalletSelected
walletIsEnabled = await window.cardano[walletName].isEnabled()
} catch (err) {
console.log(err)
}
this.setState({ walletIsEnabled })
return walletIsEnabled
}
/**
* Enables the wallet that was chosen by the user
* When this executes the user should get a window pop-up
* from the wallet asking to approve the connection
* of this app to the wallet
* #returns {Promise<boolean>}
*/
enableWallet = async () => {
const walletKey = this.state.whichWalletSelected
try {
this.API = await window.cardano[walletKey].enable()
} catch (err) {
console.log(err)
}
return this.checkIfWalletEnabled()
}
/**
* Get the API version used by the wallets
* writes the value to state
* #returns {*}
*/
getAPIVersion = () => {
const walletKey = this.state.whichWalletSelected
const walletAPIVersion = window?.cardano?.[walletKey].apiVersion
this.setState({ walletAPIVersion })
return walletAPIVersion
}
/**
* Get the name of the wallet (nami, eternl, flint)
* and store the name in the state
* #returns {*}
*/
getWalletName = () => {
const walletKey = this.state.whichWalletSelected
const walletName = window?.cardano?.[walletKey].name
this.setState({ walletName })
return walletName
}
/**
* Gets the Network ID to which the wallet is connected
* 0 = testnet
* 1 = mainnet
* Then writes either 0 or 1 to state
* #returns {Promise<void>}
*/
getNetworkId = async () => {
try {
const networkId = await this?.API?.getNetworkId()
this.setState({ networkId })
} catch (err) {
console.log(err)
}
}
/**
* Gets the UTXOs from the user's wallet and then
* stores in an object in the state
* #returns {Promise<void>}
*/
getUtxos = async () => {
let Utxos = []
try {
const rawUtxos = await this?.API?.getUtxos()
for (const rawUtxo of rawUtxos) {
const utxo = TransactionUnspentOutput.from_bytes(
Buffer.from(rawUtxo, 'hex')
)
const input = utxo.input()
const txid = Buffer.from(
input.transaction_id().to_bytes(),
'utf8'
).toString('hex')
const txindx = input.index()
const output = utxo.output()
const amount = output.amount().coin().to_str() // ADA amount in lovelace
const multiasset = output.amount().multiasset()
let multiAssetStr = ''
if (multiasset) {
const keys = multiasset.keys() // policy Ids of thee multiasset
const N = keys.len()
// console.log(`${N} Multiassets in the UTXO`)
for (let i = 0; i < N; i++) {
const policyId = keys.get(i)
const policyIdHex = Buffer.from(
policyId.to_bytes(),
'utf8'
).toString('hex')
// console.log(`policyId: ${policyIdHex}`)
const assets = multiasset.get(policyId)
const assetNames = assets.keys()
const K = assetNames.len()
// console.log(`${K} Assets in the Multiasset`)
for (let j = 0; j < K; j++) {
const assetName = assetNames.get(j)
const assetNameString = Buffer.from(
assetName.name(),
'utf8'
).toString()
const assetNameHex = Buffer.from(
assetName.name(),
'utf8'
).toString('hex')
const multiassetAmt = multiasset.get_asset(policyId, assetName)
multiAssetStr += `+ ${multiassetAmt.to_str()} + ${policyIdHex}.${assetNameHex} (${assetNameString})`
// console.log(assetNameString)
// console.log(`Asset Name: ${assetNameHex}`)
}
}
}
So I'm creating a custom backend (build on top of the http module) for fun. But I stumbled upon a problem with getting/parsing the body. I have solved it, but it still isn't what I am looking for. This is what I have now:
this.body = new Promise((resolve, reject) => {
let _d;
req.on('data', (chunk) => {
_d += chunk;
});
req.on('end', () => {
if(_d === undefined) return resolve(null);
resolve(JSON.parse(_d.replace('undefined', '')));
});
req.on('error', (err) => {
reject(err);
});
});
I have tried this without using promises but then the body will be undefined. Every time I want to access body I have to do this:
let body = req.body.then(value => {return value});
Is there a better way to do this.
Thank you for your time.
request.js:
const {IncomingMessage} = require('http');
const url = require('url');
class Request {
/**
* #param {IncomingMessage} req
*/
constructor(req) {
/**
* #type {IncomingMessage}
*/
this.http = req;
/**
* #type {Promise<{}>}
* #returns {{}}
*/
this.body = new Promise((resolve, reject) => {
let _d;
req.on('data', (chunk) => {
_d += chunk;
});
req.on('end', () => {
if(_d === undefined) return resolve(null);
resolve(JSON.parse(_d.replace('undefined', '')));
});
req.on('error', (err) => {
reject(err);
});
});
}
/**
* Get query
* #returns {import('querystring').ParsedUrlQuery}
*/
get query() {
return url.parse(this.http.url, true).query;
}
}
/**
* Get the custom request
* #param {IncomingMessage} req
* #returns {Request}
*/
function getReq(req) {
return new Request(req);
}
module.exports = {
Request,
getReq
}
backend.js:
const {Path} = require('./routes');
const {getReq, Request} = require('./request');
const {getRes, Response} = require('./response');
const {Server} = require('http');
class App {
constructor() {
this.http = require('http');
/**
* #type {Array<Path>}
* #private
*/
this.urls = []
/**
* #type {Server}
* #private
*/
this.server = new this.http.createServer(async (req, res) => {
for(var i = 0; i < this.urls.length; i++) {
/**
* #type {Path}
*/
let path = this.urls[i];
if((path.url === req.url && path.method === req.method) || (path.url === "*" && path.method === req.method)) {
path.callback(getReq(req), getRes(res));
return;
}
if(path.method === 'ALL' && (path.url === req.url || path.url === "*")) {
path.callback(getReq(req), getRes(res));
return;
}
}
});
}
/**
* #callback Callback
* #param {Request} req
* #param {Response} res
*/
/**
* Listen for GET requests
* #param {String} url
* #param {Callback} callback
*/
get(url, callback) {
this.urls.push(new Path('GET', url, callback));
}
/**
* Listen for POST requests
* #param {String} url
* #param {Callback} callback
*/
post(url, callback) {
this.urls.push(new Path('POST', url, callback));
}
/**
* Listen for all requests
* #param {String} url
* #param {Callback} callback
*/
all(url, callback) {
this.urls.push(new Path('ALL', url, callback));
}
/**
* Start the server
* #param {Number} port
* #param {Function} callback
*/
listen(port, callback = () => {}) {
this.server.listen(port);
callback();
}
/**
* Add middleware
* #param {Callback} callback
*/
middleware(callback) {
this.urls.push(new Path('ALL', '*', callback));
}
}
/**
* Create the app
* #returns {App}
*/
function createServer() {
return new App();
}
module.exports = createServer;
I am working on an react app. This has to api endpoint /dispensing and /overview from dispensing we get some data that need to be passed to /overview endpoint when component mounts show that when user logged in it should show some data.
For that I am fetching the data from /dispensing and store it redux store and inside the component I passed it to /overview but it shows error that values is not defined but it is available in when I console.log.
And when I make refresh after that it works fine. I have no idea why.
Here is my code
const DispensingIncidents = (props) => {
const classes = useStyles();
const {
getFilterData,
dispensingData,
getOverviewData,
location,
history,
getAnalysis,
} = props;
const [timeSpan, setTimeSpan] = React.useState("monthly");
const [year, setYear] = React.useState(2020);
const [tabValue, setTabValue] = React.useState(0);
const [spanData, setSpanData] = React.useState([]);
const [dataType, setDataType] = React.useState("");
const [dataTo, setDataTo] = React.useState("");
const [dataFrom, setDataFrom] = React.useState("");
// eslint-disable-next-line
const [overViewDataType, setOverViewDataType] = React.useState("");
const {
loading,
duration,
period,
type,
dispensingOverviewData,
overviewDataLoading,
incidenceAnalysisData,
analysisDataLoading,
} = dispensingData;
const { count } = dispensingOverviewData;
const { monthly } = duration;
useEffect(() => {
history.replace({
pathname: location.pathname,
search: `?year=${year}&period=${timeSpan}`,
});
setYear(year);
setTimeSpan(timeSpan);
// eslint-disable-next-line
}, [year, timeSpan]);
/**
* This updates on Year given
*/
useEffect(() => {
getFilterData(year);
}, [getFilterData, year]);
// console.log("DispesningDuration", monthly[0]); <--- this gives the data
useEffect(() => {
getOverviewData(
Object.keys(period)[3],
monthly[0].period.to, <--- Here it says [0] is not defined
monthly[0].period.from,
Object.keys(type)[0]
);
setTimeSpan(Object.keys(period)[3]);
setDataFrom(monthly[0].period.from);
setDataTo(monthly[0].period.to);
setDataType(Object.keys(type)[0]);
}, [getOverviewData, period, monthly, type]);
useEffect(() => {
getAnalysis(
count[0].key,
Object.keys(period)[3],
monthly[0].period.from,
monthly[0].period.to,
Object.keys(type)[0],
1
);
setOverViewDataType(count[0].key);
}, [count, getAnalysis, period, type, monthly]);
/**
* GET query from url search param
* #usage query.get("year")
*/
function useQuery() {
return new URLSearchParams(location.search);
}
const query = useQuery();
const time = query.get("period");
useEffect(() => {
if (time === "yearly") {
const yearlyData = duration["yearly"];
setSpanData(yearlyData);
} else if (time === "weekly") {
const weeklyData = duration["weekly"];
setSpanData(weeklyData);
} else if (time === "quarterly") {
const quarterlyData = duration["quarterly"];
setSpanData(quarterlyData);
} else if (time === "monthly") {
const monthlyData = duration["monthly"];
setSpanData(monthlyData);
} else if (time === "6 months") {
const halfYearlyData = duration["half-yearly"];
setSpanData(halfYearlyData);
}
}, [time, duration]);
/**
*
* #param {*} event
* #param {*} newValue
* on tab change
*/
// eslint-disable-next-line
const handleTabChange = (event, newValue) => {
setTabValue(newValue);
};
/**
* Year change
* #param {*} event
*/
const handleYearChange = (event) => {
setYear(event.target.value);
setTimeSpan(query.get("period"));
};
/**
* Span change
* #param {*} event
*/
const handleSpanChange = (event) => {
const value = event.target.value;
setTimeSpan(value);
};
const handleSpanTabChange = (data) => {
setDataTo(data.period.to);
setDataFrom(data.period.from);
getOverviewData(time, data.period.to, data.period.from, dataType);
};
const handleDataTypeChange = (event) => {
setDataType(event.target.value);
getOverviewData(time, dataTo, dataFrom, event.target.value);
};
const handleOverViewClick = (data) => {
getAnalysis(data, time.toLowerCase(), dataFrom, dataTo, dataType, 1);
};
I'm trying to sign a Psbt transaction from bitcoinjs-lib following what I found here:
https://github.com/helperbit/helperbit-wallet/blob/master/app/components/dashboard.wallet/bitcoin.service/ledger.ts
I've checked that the compressed publicKey both from ledger, and the one from bitcoinjsLib returned the same value.
I could sign it with the bitcoinjs-lib ECPair, but when I tries to sign it using ledger, it is always invalid.
Can someone helps me point out where did I made a mistake?
These variables is already mentioned in the code below, but for clarity purpose:
- mnemonics:
abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
- previousTx:
02000000000101869362410c61a69ab9390b2167d08219662196e869626e8b0350f1a8e4075efb0100000017160014ef3fdddccdb6b53e6dd1f5a97299a6ba2e1c11c3ffffffff0240420f000000000017a914f748afee815f78f97672be5a9840056d8ed77f4887df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702473044022061a01bf0fbac4650a9b3d035b3d9282255a5c6040aa1d04fd9b6b52ed9f4d20a022064e8e2739ef532e6b2cb461321dd20f5a5d63cf34da3056c428475d42c9aff870121025fb5240daab4cee5fa097eef475f3f2e004f7be702c421b6607d8afea1affa9b00000000
- paths:
["0'/0/0"]
- redeemScript: (non-multisig segwit)
00144328adace54072cd069abf108f97cf80420b212b
This is my minimum reproducible code I've got.
/* tslint:disable */
// #ts-check
require('regenerator-runtime');
const bip39 = require('bip39');
const { default: Transport } = require('#ledgerhq/hw-transport-node-hid');
const { default: AppBtc } = require('#ledgerhq/hw-app-btc');
const bitcoin = require('bitcoinjs-lib');
const mnemonics = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const NETWORK = bitcoin.networks.regtest;
/**
* #param {string} pk
* #returns {string}
*/
function compressPublicKey(pk) {
const { publicKey } = bitcoin.ECPair.fromPublicKey(Buffer.from(pk, 'hex'));
return publicKey.toString('hex');
}
/** #returns {Promise<any>} */
async function appBtc() {
const transport = await Transport.create();
const btc = new AppBtc(transport);
return btc;
}
const signTransaction = async() => {
const ledger = await appBtc();
const paths = ["0'/0/0"];
const [ path ] = paths;
const previousTx = "02000000000101869362410c61a69ab9390b2167d08219662196e869626e8b0350f1a8e4075efb0100000017160014ef3fdddccdb6b53e6dd1f5a97299a6ba2e1c11c3ffffffff0240420f000000000017a914f748afee815f78f97672be5a9840056d8ed77f4887df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702473044022061a01bf0fbac4650a9b3d035b3d9282255a5c6040aa1d04fd9b6b52ed9f4d20a022064e8e2739ef532e6b2cb461321dd20f5a5d63cf34da3056c428475d42c9aff870121025fb5240daab4cee5fa097eef475f3f2e004f7be702c421b6607d8afea1affa9b00000000"
const utxo = bitcoin.Transaction.fromHex(previousTx);
const segwit = utxo.hasWitnesses();
const txIndex = 0;
// ecpairs things.
const seed = await bip39.mnemonicToSeed(mnemonics);
const node = bitcoin.bip32.fromSeed(seed, NETWORK);
const ecPrivate = node.derivePath(path);
const ecPublic = bitcoin.ECPair.fromPublicKey(ecPrivate.publicKey, { network: NETWORK });
const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: ecPublic.publicKey, network: NETWORK });
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: NETWORK });
const redeemScript = p2sh.redeem.output;
const fromLedger = await ledger.getWalletPublicKey(path, { format: 'p2sh' });
const ledgerPublicKey = compressPublicKey(fromLedger.publicKey);
const bitcoinJsPublicKey = ecPublic.publicKey.toString('hex');
console.log({ ledgerPublicKey, bitcoinJsPublicKey, address: p2sh.address, segwit, fromLedger, redeemScript: redeemScript.toString('hex') });
var tx1 = ledger.splitTransaction(previousTx, true);
const psbt = new bitcoin.Psbt({ network: NETWORK });
psbt.addInput({
hash: utxo.getId(),
index: txIndex,
nonWitnessUtxo: Buffer.from(previousTx, 'hex'),
redeemScript,
});
psbt.addOutput({
address: 'mgWUuj1J1N882jmqFxtDepEC73Rr22E9GU',
value: 5000,
});
psbt.setMaximumFeeRate(1000 * 1000 * 1000); // ignore maxFeeRate we're testnet anyway.
psbt.setVersion(2);
/** #type {string} */
// #ts-ignore
const newTx = psbt.__CACHE.__TX.toHex();
console.log({ newTx });
const splitNewTx = await ledger.splitTransaction(newTx, true);
const outputScriptHex = await ledger.serializeTransactionOutputs(splitNewTx).toString("hex");
const expectedOutscriptHex = '0188130000000000001976a9140ae1441568d0d293764a347b191025c51556cecd88ac';
// stolen from: https://github.com/LedgerHQ/ledgerjs/blob/master/packages/hw-app-btc/tests/Btc.test.js
console.log({ outputScriptHex, expectedOutscriptHex, eq: expectedOutscriptHex === outputScriptHex });
const inputs = [ [tx1, 0, p2sh.redeem.output.toString('hex') /** ??? */] ];
const ledgerSignatures = await ledger.signP2SHTransaction(
inputs,
paths,
outputScriptHex,
0, // lockTime,
undefined, // sigHashType = SIGHASH_ALL ???
utxo.hasWitnesses(),
2, // version??,
);
const signer = {
network: NETWORK,
publicKey: ecPrivate.publicKey,
/** #param {Buffer} $hash */
sign: ($hash) => {
const expectedSignature = ecPrivate.sign($hash); // just for comparison.
const [ ledgerSignature0 ] = ledgerSignatures;
const decodedLedgerSignature = bitcoin.script.signature.decode(Buffer.from(ledgerSignature0, 'hex'));
console.log({
$hash: $hash.toString('hex'),
expectedSignature: expectedSignature.toString('hex'),
actualSignature: decodedLedgerSignature.signature.toString('hex'),
});
// return signature;
return decodedLedgerSignature.signature;
},
};
psbt.signInput(0, signer);
const validated = psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();
const hex = psbt.extractTransaction().toHex();
console.log({ validated, hex });
};
if (process.argv[1] === __filename) {
signTransaction().catch(console.error)
}
Ooof, finally got it working.
My mistake was I was trying to sign a p2sh-p2ms, By following a reference on how to sign a p2sh-p2wsh-p2ms.
And, also, that missing last 2 bit (01), which I think represent SIGHASH_ALL caused an error when I try to decode the signature.
this is my finalized working example.
// #ts-check
require('regenerator-runtime');
const bip39 = require('bip39');
const { default: Transport } = require('#ledgerhq/hw-transport-node-hid');
const { default: AppBtc } = require('#ledgerhq/hw-app-btc');
const serializer = require('#ledgerhq/hw-app-btc/lib/serializeTransaction');
const bitcoin = require('bitcoinjs-lib');
const mnemonics = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const NETWORK = bitcoin.networks.regtest;
const DEFAULT_LOCK_TIME = 0;
const SIGHASH_ALL = 1;
const PATHS = ["m/49'/1'/0'/0/0", "m/49'/1'/0'/0/1"];
async function appBtc() {
const transport = await Transport.create();
const btc = new AppBtc(transport);
return btc;
}
/**
* #param {string} pk
* #returns {string}
*/
function compressPublicKey(pk) {
const {
publicKey
} = bitcoin.ECPair.fromPublicKey(Buffer.from(pk, 'hex'));
return publicKey.toString('hex');
}
/**
* #param {AppBtc} ledger
* #param {bitcoin.Transaction} tx
*/
function splitTransaction(ledger, tx) {
return ledger.splitTransaction(tx.toHex(), tx.hasWitnesses());
}
const signTransaction = async() => {
const seed = await bip39.mnemonicToSeed(mnemonics);
const node = bitcoin.bip32.fromSeed(seed, NETWORK);
const signers = PATHS.map((p) => node.derivePath(p));
const publicKeys = signers.map((s) => s.publicKey);
const p2ms = bitcoin.payments.p2ms({ pubkeys: publicKeys, network: NETWORK, m: 1 });
const p2shP2ms = bitcoin.payments.p2sh({ redeem: p2ms, network: NETWORK });
const previousTx = '02000000000101588e8fc89afea9adb79de2650f0cdba762f7d0880c29a1f20e7b468f97da9f850100000017160014345766130a8f8e83aef8621122ca14fff88e6d51ffffffff0240420f000000000017a914a0546d83e5f8876045d7025a230d87bf69db893287df9de6050000000017a9142ff4aa6ffa987335c7bdba58ef4cbfecbe9e49938702483045022100c654271a891af98e46ca4d82ede8cccb0503a430e50745f959274294c98030750220331b455fed13ff4286f6db699eca06aa0c1c37c45c9f3aed3a77a3b0187ff4ac0121037ebcf3cf122678b9dc89b339017c5b76bee9fedd068c7401f4a8eb1d7e841c3a00000000';
const utxo = bitcoin.Transaction.fromHex(previousTx);
const txIndex = 0;
const destination = p2shP2ms;
const redeemScript = destination.redeem.output;
// const witnessScript = destination.redeem.redeem.output;
const ledgerRedeemScript = redeemScript;
// use witness script if the outgoing transaction was from a p2sh-p2wsh-p2ms instead of p2sh-p2ms
const fee = 1000;
/** #type {number} */
// #ts-ignore
const amount = utxo.outs[txIndex].value;
const withdrawAmount = amount - fee;
const psbt = new bitcoin.Psbt({ network: NETWORK });
const version = 1;
psbt.addInput({
hash: utxo.getId(),
index: txIndex,
nonWitnessUtxo: utxo.toBuffer(),
redeemScript,
});
psbt.addOutput({
address: '2MsK2NdiVEPCjBMFWbjFvQ39mxWPMopp5vp',
value: withdrawAmount
});
psbt.setVersion(version);
/** #type {bitcoin.Transaction} */
// #ts-ignore
const newTx = psbt.__CACHE.__TX;
const ledger = await appBtc();
const inLedgerTx = splitTransaction(ledger, utxo);
const outLedgerTx = splitTransaction(ledger, newTx);
const outputScriptHex = await serializer.serializeTransactionOutputs(outLedgerTx).toString('hex');
/** #param {string} path */
const signer = (path) => {
const ecPrivate = node.derivePath(path);
// actually only publicKey is needed, albeit ledger give an uncompressed one.
// const { publicKey: uncompressedPublicKey } = await ledger.getWalletPublicKey(path);
// const publicKey = compressPublicKey(publicKey);
return {
network: NETWORK,
publicKey: ecPrivate.publicKey,
/** #param {Buffer} $hash */
sign: async ($hash) => {
const ledgerTxSignatures = await ledger.signP2SHTransaction({
inputs: [[inLedgerTx, txIndex, ledgerRedeemScript.toString('hex')]],
associatedKeysets: [ path ],
outputScriptHex,
lockTime: DEFAULT_LOCK_TIME,
segwit: newTx.hasWitnesses(),
transactionVersion: version,
sigHashType: SIGHASH_ALL,
});
const [ ledgerSignature ] = ledgerTxSignatures;
const expectedSignature = ecPrivate.sign($hash);
const finalSignature = (() => {
if (newTx.hasWitnesses()) {
return Buffer.from(ledgerSignature, 'hex');
};
return Buffer.concat([
ledgerSignature,
Buffer.from('01', 'hex'), // SIGHASH_ALL
]);
})();
console.log({
expectedSignature: expectedSignature.toString('hex'),
finalSignature: finalSignature.toString('hex'),
});
const { signature } = bitcoin.script.signature.decode(finalSignature);
return signature;
},
};
}
await psbt.signInputAsync(0, signer(PATHS[0]));
const validate = await psbt.validateSignaturesOfAllInputs();
await psbt.finalizeAllInputs();
const hex = psbt.extractTransaction().toHex();
console.log({ validate, hex });
};
if (process.argv[1] === __filename) {
signTransaction().catch(console.error)
}
My guess is you have a space in the string passed to toByteArray function. This function doesn't trim input. Also doesn't check if input's length is even.
I have a test file with a single describe that I want to run it twice but each time I want to pass different parameters into it.
My code looks like that:
MochaRunner.js
'use strict';
const fs = require('fs');
const path = require('path');
const Mocha = require('mocha');
const uuidv1 = require('uuid/v1');
class MochaRunner {
/**
* #param {Object} options
*/
constructor(options = {}) {
this._tmps = [];
this._mocha = new Mocha(options);
}
/**
* Run tests
* #param {Array} tests
* #returns {Promise}
*/
run(tests) {
return new Promise((resolve, reject) => {
const Config = require('../config').create();
let counter = 0;
tests.forEach(test => {
const testDir = path.dirname(test.testPath);
const tmpTest = path.join(testDir,`${uuidv1()}.spec.js`);
fs.writeFileSync(tmpTest, fs.readFileSync(test.testPath));
this._tmps.push(tmpTest);
this._mocha.addFile(tmpTest);
Config.setOptions('var ' + counter++);
});
this._mocha.run((err) => {
return err ? reject(err) : resolve();
});
}).catch(() => {
return this.cleanup();
});
}
/**
* Get mocha instance
* #returns {Mocha}
*/
getMocha() {
return this._mocha;
}
/**
* Remove tmp test files
* #returns {Promise}
*/
cleanup() {
this._tmps.forEach(tmpTest => {
fs.unlinkSync(tmpTest);
});
return Promise.resolve();
}
}
module.exports = MochaRunner;
Config.js
class Config {
constructor() {
this.options = {};
}
setOptions(options){
this.options = options;
}
}
module.exports = {
_instance: null,
create: function(){
if (this._instance){
return this._instance;
}
this._instance= new Config();
return this._instance;
}
};
test.e2e.test.js
const chai = require('chai');
const Config = require('./config').create();
const assertionStyles = {
assert: chai.assert,
expect: chai.expect,
should: chai.should,
};
setImmediate(async () => {
describe('describe blah blha', function () {
console.log(Config.options);
it('test case', function () {
assertionStyles.expect(true).to.be.true;
});
});
run();
});
I do this when I want to run the above code:
let tests = ['test.e2e.test.js', 'test.e2e.test.js'];
let mocha = new MochaRunner({});
await mochaRunner.run(tests).then(() => {
mochaRunner.cleanup();
});
In the above example, I want to pass different data when the test will run for second time.
For example, I want the first file read the param from Config with the value:
'var 0'
and the second file read the param with the value:
'var 1'
Any ideas please?
Any help would be greatly appreciated!