Skip to main content
deleted 1 character in body
Source Link
Craig Ayre
  • 612
  • 4
  • 8
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));
};
Source Link
Craig Ayre
  • 612
  • 4
  • 8

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 over let 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 blocks

  • Moving BITSTAMP_PROPERTIES declaration closer to where it's assigned

  • Using 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 and secret

  • Using node-fetch instead of got to be consistent with the browser's Fetch API. JSON requests would be of the form fetch(...).then(res => res.json()).then(data => ...) instead of calling JSON.parse