const AWS = require('aws-sdk');
const crypto = require('crypto');
const got = require('got');
const CURRENCY_PAIR = 'btceur';
const TICKER_URL = `https://www.bitstamp.net/api/v2/ticker/${CURRENCY_PAIR}/`;
const BUY_LIMIT_ORDER_URL = `https://www.bitstamp.net/api/v2/buy/${CURRENCY_PAIR}/`;
if (!AWS.config.region) {
AWS.config.update({ region: 'eu-central-1' });
}
const ENCRYPTED_BITSTAMP_CUSTOMER_ID = process.env.BITSTAMP_CUSTOMER_ID;
const ENCRYPTED_BITSTAMP_KEY = process.env.BITSTAMP_KEY;
const ENCRYPTED_BITSTAMP_SECRET = process.env.BITSTAMP_SECRET;
const VALUE = Number(process.env.VALUE);
const MINIMUM_VALUE = 5;
if (isNaN(VALUE) || VALUE < MINIMUM_VALUE) {
throw new Error(`Invalid value [${value}], must be a number not less than [${MINIMUM_VALUE}].`);
}
const kms = new AWS.KMS();
const decrypt = function(encrypted) {
return kms
.decrypt({ CiphertextBlob: new Buffer(encrypted, 'base64') })
.promise()
.then(data => data.Plaintext.toString('ascii'))
};
let BITSTAMP_PROPERTIES;
const decryptBitstampProperties = function() {
return Promise.all([
decrypt(ENCRYPTED_BITSTAMP_CUSTOMER_ID),
decrypt(ENCRYPTED_BITSTAMP_KEY),
decrypt(ENCRYPTED_BITSTAMP_SECRET)
]).then(([customerId, key, secret]) =>
BITSTAMP_PROPERTIES = { customerId, key, secret };
);
})};
const doWithBitstampProperties = function() {
return BITSTAMP_PROPERTIES
? Promise.resolve(BITSTAMP_PROPERTIES)
: decryptBitstampProperties();
};
const createAuthData = function(customerId, key, secret) {
const nonce = Date.now();
const message = nonce + customerId + key;
const signature = crypto
.createHmac('sha256', new Buffer(secret, 'utf8'))
.update(message)
.digest('hex')
.toUpperCase();
return { key, signature, nonce };
};
const getAskPrice = function() {
return got(TICKET_URL).then(response => {
const result = JSON.parse(result);
const ask = Number(result.ask);
if (!isNaN(ask)) {
return ask;
}
throw new Error(`Could not parse the ask price from [${BTCEUR_TICKER_URL}] response:\n${response.body}`);
});
});
const buyLimitOrder = function(customerId, key, secret, value, askPrice) {
const amount = value / askPrice;
const roundedAmount = Math.ceil(amount * Math.pow(10,5)) / Math.pow(10,5);
const order = {
...createAuthData(customerId, key, secret),
amount: roundedAmount,
price: askPrice
};
console.log(`Buying ${roundedAmount} of BTC for price ${askPrice} for total value of ${roundedAmount * askPrice}.`);
return got.post(BUY_LIMIT_ORDER_URL, {
body: order,
form: true
}).then(response => {
return JSON.parse(response.body);
});
};
const getAskPriceAndBuyLimitOrder = function(customerId, key, secret, value) {
return getAskPrice()
.then(askPrice => buyLimitOrder(customerId, key, secret, value, askPrice));
};
exports.handler = (event, context, callback) => {
doWithBitstampProperties()
.then(bitstampProperties =>
getAskPriceAndBuyLimitOrder(bitstampProperties.customerId, bitstampProperties.key, bitstampProperties.secret, VALUE)
.then(result => callback(null, result))
.catch(error => callback(error)))
.catch(error => callback(error));
};
const AWS = require('aws-sdk');
const crypto = require('crypto');
const got = require('got');
const CURRENCY_PAIR = 'btceur';
const TICKER_URL = `https://www.bitstamp.net/api/v2/ticker/${CURRENCY_PAIR}/`;
const BUY_LIMIT_ORDER_URL = `https://www.bitstamp.net/api/v2/buy/${CURRENCY_PAIR}/`;
if (!AWS.config.region) {
AWS.config.update({ region: 'eu-central-1' });
}
const ENCRYPTED_BITSTAMP_CUSTOMER_ID = process.env.BITSTAMP_CUSTOMER_ID;
const ENCRYPTED_BITSTAMP_KEY = process.env.BITSTAMP_KEY;
const ENCRYPTED_BITSTAMP_SECRET = process.env.BITSTAMP_SECRET;
const VALUE = Number(process.env.VALUE);
const MINIMUM_VALUE = 5;
if (isNaN(VALUE) || VALUE < MINIMUM_VALUE) {
throw new Error(`Invalid value [${value}], must be a number not less than [${MINIMUM_VALUE}].`);
}
const kms = new AWS.KMS();
const decrypt = function(encrypted) {
return kms
.decrypt({ CiphertextBlob: new Buffer(encrypted, 'base64') })
.promise()
.then(data => data.Plaintext.toString('ascii'))
};
let BITSTAMP_PROPERTIES;
const decryptBitstampProperties = function() {
return Promise.all([
decrypt(ENCRYPTED_BITSTAMP_CUSTOMER_ID),
decrypt(ENCRYPTED_BITSTAMP_KEY),
decrypt(ENCRYPTED_BITSTAMP_SECRET)
]).then(([customerId, key, secret]) =>
BITSTAMP_PROPERTIES = { customerId, key, secret };
);
})};
const doWithBitstampProperties = function() {
return BITSTAMP_PROPERTIES
? Promise.resolve(BITSTAMP_PROPERTIES)
: decryptBitstampProperties();
};
const createAuthData = function(customerId, key, secret) {
const nonce = Date.now();
const message = nonce + customerId + key;
const signature = crypto
.createHmac('sha256', new Buffer(secret, 'utf8'))
.update(message)
.digest('hex')
.toUpperCase();
return { key, signature, nonce };
};
const getAskPrice = function() {
return got(TICKET_URL).then(response => {
const result = JSON.parse(result);
const ask = Number(result.ask);
if (!isNaN(ask)) {
return ask;
}
throw new Error(`Could not parse the ask price from [${BTCEUR_TICKER_URL}] response:\n${response.body}`);
});
});
const buyLimitOrder = function(customerId, key, secret, value, askPrice) {
const amount = value / askPrice;
const roundedAmount = Math.ceil(amount * Math.pow(10,5)) / Math.pow(10,5);
const order = {
...createAuthData(customerId, key, secret),
amount: roundedAmount,
price: askPrice
};
console.log(`Buying ${roundedAmount} of BTC for price ${askPrice} for total value of ${roundedAmount * askPrice}.`);
return got.post(BUY_LIMIT_ORDER_URL, {
body: order,
form: true
}).then(response => {
return JSON.parse(response.body);
});
};
const getAskPriceAndBuyLimitOrder = function(customerId, key, secret, value) {
return getAskPrice()
.then(askPrice => buyLimitOrder(customerId, key, secret, value, askPrice));
};
exports.handler = (event, context, callback) => {
doWithBitstampProperties()
.then(bitstampProperties =>
getAskPriceAndBuyLimitOrder(bitstampProperties.customerId, bitstampProperties.key, bitstampProperties.secret, VALUE)
.then(result => callback(null, result))
.catch(error => callback(error)))
.catch(error => callback(error));
};
const AWS = require('aws-sdk');
const crypto = require('crypto');
const got = require('got');
const CURRENCY_PAIR = 'btceur';
const TICKER_URL = `https://www.bitstamp.net/api/v2/ticker/${CURRENCY_PAIR}/`;
const BUY_LIMIT_ORDER_URL = `https://www.bitstamp.net/api/v2/buy/${CURRENCY_PAIR}/`;
if (!AWS.config.region) {
AWS.config.update({ region: 'eu-central-1' });
}
const ENCRYPTED_BITSTAMP_CUSTOMER_ID = process.env.BITSTAMP_CUSTOMER_ID;
const ENCRYPTED_BITSTAMP_KEY = process.env.BITSTAMP_KEY;
const ENCRYPTED_BITSTAMP_SECRET = process.env.BITSTAMP_SECRET;
const VALUE = Number(process.env.VALUE);
const MINIMUM_VALUE = 5;
if (isNaN(VALUE) || VALUE < MINIMUM_VALUE) {
throw new Error(`Invalid value [${value}], must be a number not less than [${MINIMUM_VALUE}].`);
}
const kms = new AWS.KMS();
const decrypt = function(encrypted) {
return kms
.decrypt({ CiphertextBlob: new Buffer(encrypted, 'base64') })
.promise()
.then(data => data.Plaintext.toString('ascii'))
};
let BITSTAMP_PROPERTIES;
const decryptBitstampProperties = function() {
return Promise.all([
decrypt(ENCRYPTED_BITSTAMP_CUSTOMER_ID),
decrypt(ENCRYPTED_BITSTAMP_KEY),
decrypt(ENCRYPTED_BITSTAMP_SECRET)
]).then(([customerId, key, secret]) =>
BITSTAMP_PROPERTIES = { customerId, key, secret }
);
})};
const doWithBitstampProperties = function() {
return BITSTAMP_PROPERTIES
? Promise.resolve(BITSTAMP_PROPERTIES)
: decryptBitstampProperties();
};
const createAuthData = function(customerId, key, secret) {
const nonce = Date.now();
const message = nonce + customerId + key;
const signature = crypto
.createHmac('sha256', new Buffer(secret, 'utf8'))
.update(message)
.digest('hex')
.toUpperCase();
return { key, signature, nonce };
};
const getAskPrice = function() {
return got(TICKET_URL).then(response => {
const result = JSON.parse(result);
const ask = Number(result.ask);
if (!isNaN(ask)) {
return ask;
}
throw new Error(`Could not parse the ask price from [${BTCEUR_TICKER_URL}] response:\n${response.body}`);
});
});
const buyLimitOrder = function(customerId, key, secret, value, askPrice) {
const amount = value / askPrice;
const roundedAmount = Math.ceil(amount * Math.pow(10,5)) / Math.pow(10,5);
const order = {
...createAuthData(customerId, key, secret),
amount: roundedAmount,
price: askPrice
};
console.log(`Buying ${roundedAmount} of BTC for price ${askPrice} for total value of ${roundedAmount * askPrice}.`);
return got.post(BUY_LIMIT_ORDER_URL, {
body: order,
form: true
}).then(response => {
return JSON.parse(response.body);
});
};
const getAskPriceAndBuyLimitOrder = function(customerId, key, secret, value) {
return getAskPrice()
.then(askPrice => buyLimitOrder(customerId, key, secret, value, askPrice));
};
exports.handler = (event, context, callback) => {
doWithBitstampProperties()
.then(bitstampProperties =>
getAskPriceAndBuyLimitOrder(bitstampProperties.customerId, bitstampProperties.key, bitstampProperties.secret, VALUE)
.then(result => callback(null, result))
.catch(error => callback(error)))
.catch(error => callback(error));
};
Removing Promise constructor wrappers
As @Peilonrayz mentioned in his comment, we can return the promises directly instead of wrapping them in Promise constructors.
From the MDN: Promise docs:
The constructor is primarily used to wrap functions that do not already support promises.
Since we are already returning promises (and not wrapping callbacks, for example) we can remove the constructors.
For example, doWithBitstampProperties
:
const doWithBitstampProperties = function() {
return new Promise(function(resolve, reject){
if (BITSTAMP_PROPERTIES) {
resolve(BITSTAMP_PROPERTIES);
} else {
decryptBitstampProperties().then(resolve).catch(reject);
}
});
};
Could be rewritten to return the promise directly:
const doWithBitstampProperties = function() {
if (BITSTAMP_PROPERTIES) {
return Promise.resolve(BITSTAMP_PROPERTIES);
}
return decryptBitstampProperties();
};
Or, using the conditional operator if you preferred to be more terse:
const doWithBitstampProperties = function() {
return BITSTAMP_PROPERTIES
? Promise.resolve(BITSTAMP_PROPERTIES)
: decryptBitstampProperties();
};
We can repeat this pattern for the other functions and reduce the noise that all the resolve/rejects added:
const AWS = require('aws-sdk');
const crypto = require('crypto');
const got = require('got');
const CURRENCY_PAIR = 'btceur';
const TICKER_URL = `https://www.bitstamp.net/api/v2/ticker/${CURRENCY_PAIR}/`;
const BUY_LIMIT_ORDER_URL = `https://www.bitstamp.net/api/v2/buy/${CURRENCY_PAIR}/`;
if (!AWS.config.region) {
AWS.config.update({ region: 'eu-central-1' });
}
const ENCRYPTED_BITSTAMP_CUSTOMER_ID = process.env.BITSTAMP_CUSTOMER_ID;
const ENCRYPTED_BITSTAMP_KEY = process.env.BITSTAMP_KEY;
const ENCRYPTED_BITSTAMP_SECRET = process.env.BITSTAMP_SECRET;
const VALUE = Number(process.env.VALUE);
const MINIMUM_VALUE = 5;
if (isNaN(VALUE) || VALUE < MINIMUM_VALUE) {
throw new Error(`Invalid value [${value}], must be a number not less than [${MINIMUM_VALUE}].`);
}
const kms = new AWS.KMS();
const decrypt = function(encrypted) {
return kms
.decrypt({ CiphertextBlob: new Buffer(encrypted, 'base64') })
.promise()
.then(data => data.Plaintext.toString('ascii'))
};
let BITSTAMP_PROPERTIES;
const decryptBitstampProperties = function() {
return Promise.all([
decrypt(ENCRYPTED_BITSTAMP_CUSTOMER_ID),
decrypt(ENCRYPTED_BITSTAMP_KEY),
decrypt(ENCRYPTED_BITSTAMP_SECRET)
]).then(([customerId, key, secret]) =>
BITSTAMP_PROPERTIES = { customerId, key, secret };
);
})};
const doWithBitstampProperties = function() {
return BITSTAMP_PROPERTIES
? Promise.resolve(BITSTAMP_PROPERTIES)
: decryptBitstampProperties();
};
const createAuthData = function(customerId, key, secret) {
const nonce = Date.now();
const message = nonce + customerId + key;
const signature = crypto
.createHmac('sha256', new Buffer(secret, 'utf8'))
.update(message)
.digest('hex')
.toUpperCase();
return { key, signature, nonce };
};
const getAskPrice = function() {
return got(TICKET_URL).then(response => {
const result = JSON.parse(result);
const ask = Number(result.ask);
if (!isNaN(ask)) {
return ask;
}
throw new Error(`Could not parse the ask price from [${BTCEUR_TICKER_URL}] response:\n${response.body}`);
});
});
const buyLimitOrder = function(customerId, key, secret, value, askPrice) {
const amount = value / askPrice;
const roundedAmount = Math.ceil(amount * Math.pow(10,5)) / Math.pow(10,5);
const order = {
...createAuthData(customerId, key, secret),
amount: roundedAmount,
price: askPrice
};
console.log(`Buying ${roundedAmount} of BTC for price ${askPrice} for total value of ${roundedAmount * askPrice}.`);
return got.post(BUY_LIMIT_ORDER_URL, {
body: order,
form: true
}).then(response => {
return JSON.parse(response.body);
});
};
const getAskPriceAndBuyLimitOrder = function(customerId, key, secret, value) {
return getAskPrice()
.then(askPrice => buyLimitOrder(customerId, key, secret, value, askPrice));
};
exports.handler = (event, context, callback) => {
doWithBitstampProperties()
.then(bitstampProperties =>
getAskPriceAndBuyLimitOrder(bitstampProperties.customerId, bitstampProperties.key, bitstampProperties.secret, VALUE)
.then(result => callback(null, result))
.catch(error => callback(error)))
.catch(error => callback(error));
};
A few other potential changes...
Preferring
const
overlet
to denote a variable can't be reassigned (but still mutable)Date.now()
can be used instead of(new Date()).getTime()
Returning early to prevent
else
blocksMoving
BITSTAMP_PROPERTIES
declaration closer to where it's assignedUsing the spread operator to prevent mutating
orders
(Note: this is currently a stage 3 draft at time of writing)Using template literals for more succinct strings
Using array matching instead of indices to for
customerId
,key
andsecret
Using
node-fetch
instead ofgot
to be consistent with the browser's Fetch API. JSON requests would be of the formfetch(...).then(res => res.json()).then(data => ...)
instead of callingJSON.parse