Skip to content

Commit

Permalink
Merge pull request #7 from ordinalsbot/coinbase
Browse files Browse the repository at this point in the history
add coinbase api
  • Loading branch information
ordinariusprof authored Mar 28, 2024
2 parents 5cb15a1 + da49973 commit f0c807d
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 3 deletions.
6 changes: 6 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ OKCOIN_API_PASSPHRASE=
OKCOIN_WITHDRAWAL_WALLET=
OKCOIN_DEPOSIT_WALLET=

# coinbase
COINBASE_API_KEY=
COINBASE_API_SECRET=
# for coinbase, tag is not used for withdrawal, provide the trusted withdrawal address here
COINBASE_WITHDRAWAL_WALLET=
COINBASE_DEPOSIT_WALLET=

# Notifications
# change to verbose to get all notifications
Expand Down
92 changes: 92 additions & 0 deletions api/coinbase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const axios = require('axios');
const crypto = require('crypto');

class CoinbaseAPI {
constructor(apiKey, apiSecret) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.baseURL = 'https://api.coinbase.com';
this.accountId = null;
}

async getPublicEndpoint(endpoint) {
try {
const response = await axios.get(`${this.baseURL}${endpoint}`);
return response.data;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}

async getAuthEndpoint(endpoint, body) {
try {
const headers = await this.signMessage('GET', endpoint, body);
const response = await axios.get(`${this.baseURL}${endpoint}`, { headers });
return response.data;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}

async postAuthEndpoint(endpoint, body) {
try {
const headers = await this.signMessage('POST', endpoint, JSON.stringify(body));
const response = await axios.post(`${this.baseURL}${endpoint}`, body, { headers });
return response.data;
} catch (error) {
console.error('Error:', error.message);
throw error;
}
}

async signMessage(method, endpoint, body = '') {
const timestamp = Math.floor(Date.now() / 1000); // Unix time in seconds
const message = `${timestamp}${method}${endpoint}${body}`;
const signature = crypto.createHmac('sha256', this.apiSecret).update(message).digest('hex');
return {
'CB-ACCESS-KEY': this.apiKey,
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': `${timestamp}`,
'CB-VERSION': '2024-03-22',
'Content-Type': 'application/json',
};
}

async getSystemStatus() {
throw new Error('Not implemented');
}

async getAccountId() {
const accounts = await this.getAuthEndpoint('/v2/accounts/BTC');
return accounts.data.id;
}

async getAccountBalance() {
if (!this.accountId) {
this.accountId = await this.getAccountId();
}
return this.getAuthEndpoint(`/v2/accounts/${this.accountId}`);
}

async withdrawFunds(amount, currency, address) {
if (!this.accountId) {
this.accountId = await this.getAccountId();
}
const body = {
type: 'send',
amount,
currency,
to: address,
to_financial_institution: false,
};
return this.postAuthEndpoint(`/v2/accounts/${this.accountId}/transactions`, body);
}

async getServerTime() {
return this.getPublicEndpoint('/v2/time');
}
}

module.exports = CoinbaseAPI;
29 changes: 29 additions & 0 deletions conf/satminer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ console.log('MIN_DEPOSIT_AMOUNT', MIN_DEPOSIT_AMOUNT);
let EXCHANGE_DATA = {
ACTIVE_EXCHANGE: process.env.ACTIVE_EXCHANGE,
};
let EXCHANGE_DEPOSIT_WALLET;
if (process.env.ACTIVE_EXCHANGE === 'kraken') {
const { KRAKEN_API_KEY, KRAKEN_API_SECRET } = process.env;
if (!KRAKEN_API_KEY || !KRAKEN_API_SECRET) {
Expand All @@ -51,6 +52,7 @@ if (process.env.ACTIVE_EXCHANGE === 'kraken') {
KRAKEN_WITHDRAW_CURRENCY,
KRAKEN_DEPOSIT_WALLET,
};
EXCHANGE_DEPOSIT_WALLET = KRAKEN_DEPOSIT_WALLET;
} else if (process.env.ACTIVE_EXCHANGE === 'okcoin') {
const { OKCOIN_API_KEY, OKCOIN_API_SECRET, OKCOIN_API_PASSPHRASE } = process.env;
if (!OKCOIN_API_KEY || !OKCOIN_API_SECRET || !OKCOIN_API_PASSPHRASE) {
Expand All @@ -75,6 +77,32 @@ if (process.env.ACTIVE_EXCHANGE === 'kraken') {
OKCOIN_WITHDRAW_CURRENCY,
OKCOIN_DEPOSIT_WALLET,
};
EXCHANGE_DEPOSIT_WALLET = OKCOIN_DEPOSIT_WALLET;
} else if (process.env.ACTIVE_EXCHANGE === 'coinbase') {
const { COINBASE_API_KEY, COINBASE_API_SECRET } = process.env;
if (!COINBASE_API_KEY || !COINBASE_API_SECRET) {
throw Error('Missing COINBASE_API_KEY or COINBASE_API_SECRET environment variable');
}
const { COINBASE_WITHDRAWAL_WALLET } = process.env;
if (!COINBASE_WITHDRAWAL_WALLET) {
throw Error('Missing COINBASE_WITHDRAWAL_WALLET environment variable');
}
console.log('COINBASE_WITHDRAWAL_WALLET', COINBASE_WITHDRAWAL_WALLET);
const COINBASE_WITHDRAW_CURRENCY = 'BTC';
const { COINBASE_DEPOSIT_WALLET } = process.env;
if (!COINBASE_DEPOSIT_WALLET) {
throw new Error('Missing COINBASE_DEPOSIT_WALLET environment variable');
}
EXCHANGE_DATA = {
...EXCHANGE_DATA,
COINBASE_API_KEY,
COINBASE_API_SECRET,
COINBASE_WITHDRAWAL_WALLET,
COINBASE_WITHDRAW_CURRENCY,
COINBASE_DEPOSIT_WALLET,
};
EXCHANGE_DEPOSIT_WALLET = COINBASE_DEPOSIT_WALLET;

} else {
throw new Error('Unknown exchange');
}
Expand Down Expand Up @@ -178,6 +206,7 @@ console.log('CUSTOM_SPECIAL_SAT_WALLETS', CUSTOM_SPECIAL_SAT_WALLETS);
module.exports = {
ORDINALSBOT_API_KEY,
EXCHANGE_DATA,
EXCHANGE_DEPOSIT_WALLET,
MAX_WITHDRAWAL_AMOUNT,
MIN_WITHDRAWAL_AMOUNT,
MIN_DEPOSIT_AMOUNT,
Expand Down
21 changes: 18 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ const { Satscanner, Satextractor, Mempool } = require('ordinalsbot');
const Satminer = require('./model/satminer');
const KrakenAPI = require('./api/kraken');
const OkcoinAPI = require('./api/okcoin');
const CoinbaseAPI = require('./api/coinbase');
const MempoolApi = require('./api/mempool');
const KrakenTumbler = require('./model/exchanges/kraken');
const OkcoinTumbler = require('./model/exchanges/okcoin');
const CoinbaseTumbler = require('./model/exchanges/coinbase');
const Wallet = require('./model/wallet');
const { loadBitcoinWallet } = require('./utils/funcs');
const {
EXCHANGE_DATA,
EXCHANGE_DEPOSIT_WALLET,
MAX_WITHDRAWAL_AMOUNT,
MIN_WITHDRAWAL_AMOUNT,
MIN_DEPOSIT_AMOUNT,
Expand Down Expand Up @@ -70,13 +73,15 @@ const {
KRAKEN_API_SECRET,
KRAKEN_WITHDRAWAL_WALLET,
KRAKEN_WITHDRAW_CURRENCY,
KRAKEN_DEPOSIT_WALLET,
OKCOIN_API_KEY,
OKCOIN_API_SECRET,
OKCOIN_API_PASSPHRASE,
OKCOIN_WITHDRAWAL_WALLET,
OKCOIN_WITHDRAW_CURRENCY,
OKCOIN_DEPOSIT_WALLET,
COINBASE_API_KEY,
COINBASE_API_SECRET,
COINBASE_WITHDRAWAL_WALLET,
COINBASE_WITHDRAW_CURRENCY,
} = EXCHANGE_DATA;

const sweepConfirmationTargetBlocks = 1;
Expand All @@ -86,7 +91,7 @@ const satminer = new Satminer(
satextractor,
TUMBLER_ADDRESS,
INVENTORY_WALLET,
ACTIVE_EXCHANGE === 'kraken' ? KRAKEN_DEPOSIT_WALLET : OKCOIN_DEPOSIT_WALLET,
EXCHANGE_DEPOSIT_WALLET,
sweepConfirmationTargetBlocks,
MIN_DEPOSIT_AMOUNT,
notifications,
Expand Down Expand Up @@ -118,6 +123,16 @@ switch (ACTIVE_EXCHANGE) {
OKCOIN_WITHDRAW_CURRENCY,
);
break;
case 'coinbase':
const coinbaseAPI = new CoinbaseAPI(COINBASE_API_KEY, COINBASE_API_SECRET);
exchangeTumbler = new CoinbaseTumbler(
coinbaseAPI,
MIN_WITHDRAWAL_AMOUNT,
MAX_WITHDRAWAL_AMOUNT,
COINBASE_WITHDRAWAL_WALLET,
COINBASE_WITHDRAW_CURRENCY,
);
break;
default:
throw new Error(`Unknown exchange ${ACTIVE_EXCHANGE}`);
}
Expand Down
65 changes: 65 additions & 0 deletions model/exchanges/coinbase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const CoinbaseAPI = require('../../api/coinbase');

/**
* Rotates funds from a Coinbase account
*/
class CoinbaseTumbler {
/**
* @param {CoinbaseAPI} coinbaseClient
* @param {number} minWithdrawalAmount
* @param {number} maxWithdrawalAmount
* @param {string} withdrawWallet
* @param {string} withdrawCurrency
*/
constructor(
coinbaseClient,
minWithdrawalAmount,
maxWithdrawalAmount,
withdrawWallet,
withdrawCurrency,
) {
this.coinbaseClient = coinbaseClient;
this.minWithdrawalAmount = minWithdrawalAmount;
this.maxWithdrawalAmount = maxWithdrawalAmount;
this.withdrawWallet = withdrawWallet;
this.withdrawCurrency = withdrawCurrency;
}

withdrawAvailableFunds = async () => {
const balance = await this.coinbaseClient.getAccountBalance();

const btcBalance = balance.data.balance.amount;
if (btcBalance < this.minWithdrawalAmount) {
console.log(`insufficient funds to withdraw, account balance ${btcBalance}`);
return false;
}

let withdrawalAmount = Number(btcBalance).toFixed(8);;
if (btcBalance > this.maxWithdrawalAmount) {
withdrawalAmount = this.maxWithdrawalAmount;
}
// deduct some random fee
withdrawalAmount -= 0.001;
withdrawalAmount = Number(withdrawalAmount).toFixed(8);

console.log(
`withdrawing ${withdrawalAmount} ${this.withdrawCurrency} to wallet ${this.withdrawWallet}`,
);

const res = await this.coinbaseClient.withdrawFunds(
`${withdrawalAmount}`,
this.withdrawCurrency,
this.withdrawWallet,
);

if (!res.data?.id) {
console.error('error calling coinbase api', res);
return false;
}

console.log('successful withdrawal from coinbase');
return true;
};
}

module.exports = CoinbaseTumbler;

0 comments on commit f0c807d

Please sign in to comment.