Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e133d56072 | ||
|
|
fa7fe94d6d | ||
|
|
7fd9fe1043 | ||
|
|
0e571e8965 | ||
|
|
d1b2052494 | ||
|
|
a9694ac1dd | ||
|
|
c387bb3ea3 | ||
|
|
4a2fb94dc4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.env
|
.env
|
||||||
node_modules
|
node_modules
|
||||||
|
stacksats.sh
|
||||||
|
|||||||
126
README.md
126
README.md
@@ -13,6 +13,16 @@ Also the version is fixed, so that unwanted changes do not slip in.
|
|||||||
|
|
||||||
However: Use this at your own risk and decide for yourself whether or not you want to run this script and its dependencies!
|
However: Use this at your own risk and decide for yourself whether or not you want to run this script and its dependencies!
|
||||||
|
|
||||||
|
## 🔑 API Key
|
||||||
|
|
||||||
|
Obtain your Kraken API Key via [the API settings page](https://www.kraken.com/u/settings/api).
|
||||||
|
Generate a new API key dedicated for stacking using the "Query Funds" and "Modify Orders" permissions:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Only check the "Withdraw Funds" option if you plan to automatically withdraw Bitcoin from Kraken.
|
||||||
|
See details below.
|
||||||
|
|
||||||
## 📦 Setup
|
## 📦 Setup
|
||||||
|
|
||||||
Prerequisite: At least the current LTS version of [Node.js](https://nodejs.org/).
|
Prerequisite: At least the current LTS version of [Node.js](https://nodejs.org/).
|
||||||
@@ -26,16 +36,26 @@ npm install
|
|||||||
Setup the environment variables for the script:
|
Setup the environment variables for the script:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
# used to authenticate with Kraken
|
||||||
export KRAKEN_API_KEY="apiKeyFromTheKrakenSettings"
|
export KRAKEN_API_KEY="apiKeyFromTheKrakenSettings"
|
||||||
export KRAKEN_API_SECRET="privateKeyFromTheKrakenSettings"
|
export KRAKEN_API_SECRET="privateKeyFromTheKrakenSettings"
|
||||||
|
|
||||||
|
# used for buying
|
||||||
export KRAKEN_API_FIAT="USD" # the governmental shitcoin you are selling
|
export KRAKEN_API_FIAT="USD" # the governmental shitcoin you are selling
|
||||||
export KRAKEN_BUY_AMOUNT=21 # fiat amount you trade for the future of money
|
export KRAKEN_BUY_AMOUNT=21 # fiat amount you trade for the future of money
|
||||||
|
|
||||||
|
# used for withdrawal
|
||||||
|
export KRAKEN_MAX_REL_FEE=0.5 # maximum fee in % that you are willing to pay
|
||||||
|
export KRAKEN_WITHDRAW_KEY="descriptionOfWithdrawalAddress"
|
||||||
|
|
||||||
|
# remove this line after verifying everything works
|
||||||
|
export KRAKEN_DRY_RUN_PLACE_NO_ORDER=1
|
||||||
```
|
```
|
||||||
|
|
||||||
Use a dry run to test the script and see the output without placing an order:
|
Use a dry run to test the script and see the output without placing an order:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm test
|
npm run test:stack
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see something like this sample output:
|
You should see something like this sample output:
|
||||||
@@ -55,13 +75,13 @@ You should see something like this sample output:
|
|||||||
When you are good to go, execute this command in a regular interval:
|
When you are good to go, execute this command in a regular interval:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm run stack-sats
|
npm run stack
|
||||||
```
|
```
|
||||||
|
|
||||||
The best and easiest way is to wrap it all up in a shell script.
|
The best and easiest way is to wrap it all up in a shell script.
|
||||||
This script can be triggered via cron job, e.g. weekly, daily or hourly.
|
This script can be triggered via cron job, e.g. weekly, daily or hourly.
|
||||||
|
|
||||||
Here's a sample `stack-sats.sh` script:
|
Here's a sample `stacksats.sh` script:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
@@ -74,19 +94,111 @@ export KRAKEN_API_KEY="apiKeyFromTheKrakenSettings"
|
|||||||
export KRAKEN_API_SECRET="privateKeyFromTheKrakenSettings"
|
export KRAKEN_API_SECRET="privateKeyFromTheKrakenSettings"
|
||||||
export KRAKEN_API_FIAT="USD"
|
export KRAKEN_API_FIAT="USD"
|
||||||
export KRAKEN_BUY_AMOUNT=21
|
export KRAKEN_BUY_AMOUNT=21
|
||||||
|
export KRAKEN_MAX_REL_FEE=0.5
|
||||||
|
export KRAKEN_WITHDRAW_KEY="descriptionOfWithdrawalAddress"
|
||||||
|
export KRAKEN_DRY_RUN_PLACE_NO_ORDER=1
|
||||||
|
|
||||||
|
# run script
|
||||||
BASE_DIR=$(cd `dirname $0` && pwd)
|
BASE_DIR=$(cd `dirname $0` && pwd)
|
||||||
cd $BASE_DIR/stacking-sats-kraken
|
cd $BASE_DIR/stacking-sats-kraken
|
||||||
result=$(npm run stack-sats 2>&1)
|
cmd=${1:-"stack"}
|
||||||
echo $result
|
|
||||||
|
|
||||||
# Optional: Send yourself an email
|
if [[ "${KRAKEN_DRY_RUN_PLACE_NO_ORDER}" ]]; then
|
||||||
|
result=$(npm run test:$cmd --silent 2>&1)
|
||||||
|
else
|
||||||
|
result=$(npm run $cmd --silent 2>&1)
|
||||||
|
fi
|
||||||
|
echo "$result"
|
||||||
|
|
||||||
|
# optional: send yourself an email
|
||||||
recipient="satstacker@example.org"
|
recipient="satstacker@example.org"
|
||||||
echo "Subject: Sats got stacked
|
echo "Subject: Sats got stacked
|
||||||
From: satstacker@example.org
|
From: satstacker@example.org
|
||||||
To: $recipient $result" | /usr/sbin/sendmail $recipient
|
To: $recipient $result" | /usr/sbin/sendmail $recipient
|
||||||
```
|
```
|
||||||
|
|
||||||
Make it executable with `chmod +x stack-sats.sh` and go wild.
|
Make it executable with `chmod +x stacksats.sh` and go wild.
|
||||||
|
|
||||||
[Stay humble!](https://twitter.com/matt_odell/status/1117222441867194374) 🙏
|
[Stay humble!](https://twitter.com/matt_odell/status/1117222441867194374) 🙏
|
||||||
|
|
||||||
|
## 🔑 Withdrawal
|
||||||
|
|
||||||
|
Holding significant amounts on an exchange is never a good idea.
|
||||||
|
You should regularly take ownership of your coins by withdrawing to your own wallet.
|
||||||
|
This can either be done manually or it can be automated.
|
||||||
|
The script provided here will only withdraw to a previously defined Bitcoin address if the relative fees do not exceed a certain limit.
|
||||||
|
|
||||||
|
*It is optional to run the withdrawal script.*
|
||||||
|
|
||||||
|
### Example 1
|
||||||
|
|
||||||
|
- Max. relative fee: 0.5%
|
||||||
|
- Fixed Kraken fee: ₿ 0.00050
|
||||||
|
- Balance: ₿ 0.06000
|
||||||
|
➡️ No withdrawal since fee actual (0.83%) is too high
|
||||||
|
|
||||||
|
### Example 2
|
||||||
|
|
||||||
|
- Max. relative fee: 0.5%
|
||||||
|
- Fixed Kraken fee: ₿ 0.00050
|
||||||
|
- Balance: ₿ 0.12000
|
||||||
|
➡️ Withdrawal executed since actual fee (0.42%) is low enough
|
||||||
|
|
||||||
|
In case you plan to automatically withdraw from Kraken, a withdrawal method must first be defined.
|
||||||
|
If you already set up a methode you can reuse it.
|
||||||
|
Otherwise generate a new one by going to **Funding > Bitcoin (XBT) withdraw > Add address**.
|
||||||
|
The description field will later be used as an environment variable in the script.
|
||||||
|
|
||||||
|
To test the withdrawal of funds to your defined address run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run test:withdraw
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see something like this:
|
||||||
|
|
||||||
|
```text
|
||||||
|
💡 Relative fee of withdrawal amount: 5.57%
|
||||||
|
❌ Fee is too high – max rel. fee: 0.50%
|
||||||
|
```
|
||||||
|
|
||||||
|
It is recommended to run the withdrawal script every time you stacked sats:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run withdraw
|
||||||
|
```
|
||||||
|
|
||||||
|
Since it can take a couple seconds or minutes for your order to fill, you should run the following script a couple hours later after the stacking script.
|
||||||
|
Just set up a second cron job which executes the withdrawal script.
|
||||||
|
|
||||||
|
If you are using the aforementioned `stacksats.sh` script you can withdraw via this command:
|
||||||
|
`stacksats.sh withdraw`
|
||||||
|
|
||||||
|
## ⚡️ RaspiBlitz Integration
|
||||||
|
|
||||||
|
This script ships with the [RaspiBlitz](https://github.com/rootzoll/raspiblitz) (v1.6 and above).
|
||||||
|
|
||||||
|
You can enable it via the Console of your RaspiBlitz.
|
||||||
|
Leave the main menu via the last option "Console" and use the following commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# enable the script
|
||||||
|
./config.scripts/bonus.stacking-sats-kraken.sh on
|
||||||
|
|
||||||
|
# switch to the stackingsats user
|
||||||
|
sudo su - stackingsats
|
||||||
|
|
||||||
|
# edit your configuration (see "Setup" above)
|
||||||
|
nano /mnt/hdd/app-data/stacking-sats-kraken/.env
|
||||||
|
|
||||||
|
# follow the instructions from the first step to set up a cronjob
|
||||||
|
crontab -e
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is an example for a daily cronjob at 6:15am ...
|
||||||
|
|
||||||
|
```sh
|
||||||
|
SHELL=/bin/bash
|
||||||
|
PATH=/bin:/usr/sbin:/usr/bin:/usr/local/bin
|
||||||
|
15 6 * * * /home/stackingsats/stacksats.sh > /dev/null 2>&1
|
||||||
|
```
|
||||||
|
|||||||
BIN
api-permissions.png
Normal file
BIN
api-permissions.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
34
commands/stack.js
Normal file
34
commands/stack.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
module.exports = async (kraken, validate, getEnv) => {
|
||||||
|
const [fiat, amount] = getEnv('KRAKEN_API_FIAT', 'KRAKEN_BUY_AMOUNT')
|
||||||
|
|
||||||
|
// https://www.kraken.com/features/api
|
||||||
|
const crypto = 'XBT'
|
||||||
|
const pair = `${crypto}${fiat}`
|
||||||
|
|
||||||
|
// Fetch and display information
|
||||||
|
const { result: balance } = await kraken.api('Balance')
|
||||||
|
const { result: ticker } = await kraken.api('Ticker', { pair })
|
||||||
|
|
||||||
|
const fiatBalance = balance[`Z${fiat}`] || balance[fiat] || 0.0
|
||||||
|
const cryptoBalance = balance[`X${crypto}`] || balance[crypto] || 0.0
|
||||||
|
const [{ a: [a], b: [b] }] = Object.values(ticker)
|
||||||
|
const ask = parseFloat(a)
|
||||||
|
const bid = parseFloat(b)
|
||||||
|
const price = bid
|
||||||
|
|
||||||
|
// Calculate volume and adjust precision
|
||||||
|
const volume = (amount / price).toFixed(8)
|
||||||
|
|
||||||
|
console.log('💰 Balance:', fiatBalance, fiat, '/', cryptoBalance, crypto, '\n')
|
||||||
|
console.log('📈 Ask:', ask, fiat)
|
||||||
|
console.log('📉 Bid:', bid, fiat, '\n')
|
||||||
|
|
||||||
|
// Place order
|
||||||
|
const details = { pair, type: 'buy', ordertype: 'limit', price, volume }
|
||||||
|
if (validate) details.validate = true
|
||||||
|
|
||||||
|
const { result: { descr: { order }, txid } } = await kraken.api('AddOrder', details)
|
||||||
|
|
||||||
|
console.log('💸 Order:', order)
|
||||||
|
if (txid) console.log('📎 Transaction ID:', txid.join(', '))
|
||||||
|
}
|
||||||
25
commands/withdraw.js
Normal file
25
commands/withdraw.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module.exports = async (kraken, validate, getEnv) => {
|
||||||
|
const [maxFee, key] = getEnv('KRAKEN_MAX_REL_FEE', 'KRAKEN_WITHDRAW_KEY')
|
||||||
|
|
||||||
|
// https://api.kraken.com/0/private/WithdrawInfo
|
||||||
|
const asset = 'XBT'
|
||||||
|
const withdrawdetails = { asset, key, amount: 0 }
|
||||||
|
|
||||||
|
// Get withdrawal information
|
||||||
|
const { result: { limit, fee } } = await kraken.api('WithdrawInfo', withdrawdetails)
|
||||||
|
const relFee = 1/parseFloat(limit)*parseFloat(fee)
|
||||||
|
|
||||||
|
console.log(`💡 Relative fee of withdrawal amount: ${(relFee*100).toFixed(2)}%`)
|
||||||
|
|
||||||
|
// Place withdrawal when fee is low enough (relatively)
|
||||||
|
if (relFee < maxFee/100) {
|
||||||
|
console.log(`💰 Withdraw ${limit} ${asset} now.`)
|
||||||
|
const withdraw = { asset, key, amount: limit }
|
||||||
|
if (!validate) {
|
||||||
|
const { result: { refid } } = await kraken.api('Withdraw', withdraw)
|
||||||
|
if (refid) console.log(`📎 Withdrawal reference ID: ${refid}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(`❌ Fee is too high - max rel. fee: ${parseFloat(maxFee).toFixed(2)}%`)
|
||||||
|
}
|
||||||
|
}
|
||||||
49
index.js
49
index.js
@@ -1,48 +1,23 @@
|
|||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
const Kraken = require('kraken-api')
|
const Kraken = require('kraken-api')
|
||||||
|
|
||||||
const {
|
const getEnv = (...vars) => vars.map(name => {
|
||||||
KRAKEN_API_KEY: key,
|
const value = process.env[name]
|
||||||
KRAKEN_API_SECRET: secret,
|
assert(value, `Provide the ${name} environment variable.`)
|
||||||
KRAKEN_API_FIAT: fiat,
|
return value
|
||||||
KRAKEN_BUY_AMOUNT: amount
|
})
|
||||||
} = process.env
|
const command = process.argv[2].replace('--cmd=', '')
|
||||||
|
const validate = process.argv.includes('--validate') || process.env['KRAKEN_DRY_RUN_PLACE_NO_ORDER']
|
||||||
assert(key && secret, 'Provide the KRAKEN_API_KEY and KRAKEN_API_SECRET environment variables.')
|
|
||||||
assert(fiat && amount, 'Provide the KRAKEN_API_FIAT and KRAKEN_BUY_AMOUNT environment variables.')
|
|
||||||
|
|
||||||
// https://www.kraken.com/features/api
|
|
||||||
const kraken = new Kraken(key, secret)
|
|
||||||
const crypto = 'XBT'
|
|
||||||
const pair = `X${crypto}Z${fiat}`
|
|
||||||
const validate = process.argv[2] === '--validate'
|
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
// Fetch and display information
|
|
||||||
const { result: { [`Z${fiat}`]: fiatBalance, [`X${crypto}`]: cryptoBalance } } = await kraken.api('Balance')
|
|
||||||
const { result: { [pair]: { a: [a], b: [b] } } } = await kraken.api('Ticker', { pair })
|
|
||||||
|
|
||||||
const ask = parseFloat(a)
|
|
||||||
const bid = parseFloat(b)
|
|
||||||
const price = bid
|
|
||||||
|
|
||||||
// Calculate volume and adjust precision
|
|
||||||
const volume = (amount / price).toFixed(8)
|
|
||||||
|
|
||||||
console.log('💰 Balance:', fiatBalance, fiat, '/', cryptoBalance, crypto, '\n')
|
|
||||||
console.log('📈 Ask:', ask, fiat)
|
|
||||||
console.log('📉 Bid:', bid, fiat, '\n')
|
|
||||||
|
|
||||||
// Place order
|
|
||||||
try {
|
try {
|
||||||
const details = { pair, type: 'buy', ordertype: 'limit', price, volume }
|
const [apiKey, secret] = getEnv('KRAKEN_API_KEY', 'KRAKEN_API_SECRET')
|
||||||
if (validate) details.validate = true
|
const kraken = new Kraken(apiKey, secret)
|
||||||
|
|
||||||
const { result: { descr: { order }, txid } } = await kraken.api('AddOrder', details)
|
const cmd = require(`./commands/${command}`)
|
||||||
|
await cmd(kraken, validate, getEnv)
|
||||||
|
|
||||||
console.log('💸 Order:', order)
|
if (validate) console.log('\n🚨 THIS WAS JUST A VALIDATION RUN!')
|
||||||
if (txid) console.log('📎 Transaction ID:', txid.join(', '))
|
|
||||||
if (validate) console.log('\n🚨 THIS WAS JUST A VALIDATION RUN, NO ORDER HAS BEEN PLACED!')
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`\n🚨 Failure:`, err.message)
|
console.log(`\n🚨 Failure:`, err.message)
|
||||||
}
|
}
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "stacking-sats-kraken",
|
"name": "stacking-sats-kraken",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,14 +1,16 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "stacking-sats-kraken",
|
"name": "stacking-sats-kraken",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Use the Kraken API to stack sats",
|
"description": "Use the Kraken API to stack sats",
|
||||||
"author": "Dennis Reimann <mail@dennisreimann.de>",
|
"author": "Dennis Reimann <mail@dennisreimann.de>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "index.js",
|
"main": "stack.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"stack-sats": "node index.js",
|
"stack": "node index.js --cmd=stack",
|
||||||
"test": "node index.js --validate"
|
"withdraw": "node index.js --cmd=withdraw",
|
||||||
|
"test:stack": "node index.js --cmd=stack --validate",
|
||||||
|
"test:withdraw": "node index.js --cmd=withdraw --validate"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"kraken-api": "1.0.0"
|
"kraken-api": "1.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user