From 3046a4a3c8198108456a16bd95ae503ac2a0bafd Mon Sep 17 00:00:00 2001 From: Gert-Jaap Glasbergen Date: Thu, 2 May 2019 08:13:25 +0200 Subject: [PATCH 1/4] Coinzark work in progress --- src/index.js | 2 + src/swap/coinzark.js | 106 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/swap/coinzark.js diff --git a/src/index.js b/src/index.js index 56340ad8..910186bc 100644 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ import { makeNomicsPlugin } from './rate/nomics.js' import { makeShapeshiftRatePlugin } from './rate/shapeshift-rate.js' import { makeChangellyPlugin } from './swap/changelly.js' import { makeChangeNowPlugin } from './swap/changenow.js' +import { makeCoinZarkPlugin } from './swap/coinzark.js' import { makeFaastPlugin } from './swap/faast.js' import { makeShapeshiftPlugin } from './swap/shapeshift.js' @@ -26,6 +27,7 @@ const edgeCorePlugins = { changelly: makeChangellyPlugin, changenow: makeChangeNowPlugin, faast: makeFaastPlugin, + coinzark: makeCoinZarkPlugin, shapeshift: makeShapeshiftPlugin } diff --git a/src/swap/coinzark.js b/src/swap/coinzark.js new file mode 100644 index 00000000..39b7daca --- /dev/null +++ b/src/swap/coinzark.js @@ -0,0 +1,106 @@ +// @flow + +import { + type EdgeCorePluginOptions, + type EdgeSwapPlugin, + type EdgeSwapPluginQuote, + type EdgeSwapRequest, + SwapCurrencyError +} from 'edge-core-js/types' + +import { makeSwapPluginQuote } from '../swap-helpers.js' + +const swapInfo = { + pluginName: 'coinzark', + displayName: 'CoinZark', + supportEmail: 'support@coinzark.com' +} + +/* type CoinZarkQuoteJson = { + swap_id: string, + created_at: string, + deposit_address: string, + deposit_amount: number, + deposit_currency: string, + spot_price: number, + price: number, + price_locked_at: string, + price_locked_until: string, + withdrawal_amount: number, + withdrawal_address: string, + withdrawal_currency: string, + refund_address?: string, + user_id?: string, + terms?: string +} + +const dontUseLegacy = { + DGB: true +} + +async function getAddress (wallet: EdgeCurrencyWallet, currencyCode: string) { + const addressInfo = await wallet.getReceiveAddress({ currencyCode }) + return addressInfo.legacyAddress && !dontUseLegacy[currencyCode] + ? addressInfo.legacyAddress + : addressInfo.publicAddress +} */ + +export function makeCoinZarkPlugin ( + opts: EdgeCorePluginOptions +): EdgeSwapPlugin { + const { io, initOptions } = opts + + const out: EdgeSwapPlugin = { + swapInfo, + async fetchSwapQuote ( + request: EdgeSwapRequest, + userSettings: Object | void + ): Promise { + const { + fromCurrencyCode, + fromWallet, + nativeAmount, + quoteFor, + toCurrencyCode, + toWallet + } = request + if (toCurrencyCode === fromCurrencyCode) { + throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + } + + io.console.info(initOptions, fromWallet, nativeAmount, quoteFor, toWallet) + + const spendInfo = { + currencyCode: request.fromCurrencyCode, + spendTargets: [ + { + nativeAmount: '0', + publicAddress: '', + otherParams: { + uniqueIdentifier: '' + } + } + ] + } + io.console.info('CoinZark spendInfo', spendInfo) + const tx = await request.fromWallet.makeSpend(spendInfo) + tx.otherParams.payinAddress = spendInfo.spendTargets[0].publicAddress + tx.otherParams.uniqueIdentifier = + spendInfo.spendTargets[0].otherParams.uniqueIdentifier + + // Convert that to the output format: + return makeSwapPluginQuote( + request, + '', + '', + tx, + '', + 'CoinZark', + new Date(), + '' + ) + } + } + + return out +} From 6561ef3d7825dfc14005278ab9be34a011879b1e Mon Sep 17 00:00:00 2001 From: Gert-Jaap Glasbergen Date: Thu, 2 May 2019 12:23:33 +0200 Subject: [PATCH 2/4] CoinZark Swap logic --- src/swap/coinzark.js | 208 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 178 insertions(+), 30 deletions(-) diff --git a/src/swap/coinzark.js b/src/swap/coinzark.js index 39b7daca..7c36251b 100644 --- a/src/swap/coinzark.js +++ b/src/swap/coinzark.js @@ -2,12 +2,16 @@ import { type EdgeCorePluginOptions, + type EdgeCurrencyWallet, type EdgeSwapPlugin, type EdgeSwapPluginQuote, type EdgeSwapRequest, + SwapAboveLimitError, + SwapBelowLimitError, SwapCurrencyError } from 'edge-core-js/types' +import { getFetchJson } from '../react-native-io.js' import { makeSwapPluginQuote } from '../swap-helpers.js' const swapInfo = { @@ -16,39 +20,29 @@ const swapInfo = { supportEmail: 'support@coinzark.com' } -/* type CoinZarkQuoteJson = { - swap_id: string, - created_at: string, - deposit_address: string, - deposit_amount: number, - deposit_currency: string, - spot_price: number, - price: number, - price_locked_at: string, - price_locked_until: string, - withdrawal_amount: number, - withdrawal_address: string, - withdrawal_currency: string, - refund_address?: string, - user_id?: string, - terms?: string -} +const expirationMs = 84600 * 60 * 60 * 1000 + +const uri = 'https://www.coinzark.com/api/v2/' const dontUseLegacy = { DGB: true } -async function getAddress (wallet: EdgeCurrencyWallet, currencyCode: string) { +async function getAddress ( + wallet: EdgeCurrencyWallet, + currencyCode: string +): Promise { const addressInfo = await wallet.getReceiveAddress({ currencyCode }) return addressInfo.legacyAddress && !dontUseLegacy[currencyCode] ? addressInfo.legacyAddress : addressInfo.publicAddress -} */ +} export function makeCoinZarkPlugin ( opts: EdgeCorePluginOptions ): EdgeSwapPlugin { const { io, initOptions } = opts + const fetchJson = getFetchJson(opts) const out: EdgeSwapPlugin = { swapInfo, @@ -68,36 +62,190 @@ export function makeCoinZarkPlugin ( throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) } - io.console.info(initOptions, fromWallet, nativeAmount, quoteFor, toWallet) + if (quoteFor !== 'from') { + // CoinZark does not support reverse quotes + throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + } + + // Grab addresses: + const [fromAddress, toAddress] = await Promise.all([ + getAddress(fromWallet, fromCurrencyCode), + getAddress(toWallet, toCurrencyCode) + ]) + + const quoteAmount = await fromWallet.nativeToDenomination( + nativeAmount, + fromCurrencyCode + ) + + async function get (path: string) { + const api = `${uri}${path}` + const reply = await fetchJson(api) + return reply.json + } + + async function post (url, values: any) { + const opts = { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Accept: 'application/json' + }, + method: 'POST', + body: '' + } + const formData = new URLSearchParams() + for (const prop in values) { + if (!values.hasOwnProperty(prop)) continue + formData.append(prop, values[prop]) + } + opts.body = formData.toString() + const reply = await fetchJson(`${uri}${url}`, opts) + const out = reply.json + return out + } + + const currencies = await get('swap/currencies') + let fromCorrect = false + let toCorrect = false + if (!(currencies === null || currencies.result === null)) { + for (const curr of currencies.result) { + io.console.info( + `curr.id [${curr.id}] - curr.canDeposit [${ + curr.canDeposit + }] - curr.canReceive [${curr.canReceive}]` + ) + if (curr.id === fromCurrencyCode && curr.canDeposit === 1) { + fromCorrect = true + } + + if (curr.id === toCurrencyCode && curr.canReceive === 1) { + toCorrect = true + } + } + } + + if (!fromCorrect || !toCorrect) { + throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + } + + const swapRate = await get( + 'swap/rate?from=' + + fromCurrencyCode + + '&to=' + + toCurrencyCode + + '&amount=' + + quoteAmount + + '&affiliateID=' + + initOptions.affiliateId + + '&affiliateFee=' + + initOptions.affiliateFee.toString() + ) + + const nativeMin = await request.fromWallet.denominationToNative( + swapRate.result.minimumDeposit, + fromCurrencyCode + ) + + const nativeMax = await request.fromWallet.denominationToNative( + swapRate.result.maximumDeposit, + fromCurrencyCode + ) + + if (swapRate.result.finalAmount === 0) { + if ( + parseFloat(swapRate.result.depositAmount) < + parseFloat(swapRate.result.minimumDeposit) + ) { + throw new SwapBelowLimitError(swapInfo, nativeMin) + } + + if ( + parseFloat(swapRate.result.depositAmount) > + parseFloat(swapRate.result.maximumDeposit) + ) { + throw new SwapAboveLimitError(swapInfo, nativeMax) + } + + throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + } + + const receiveAmount = await fromWallet.denominationToNative( + swapRate.result.finalAmount, + fromCurrencyCode + ) + + const swapParams = { + destination: toAddress, + refund: fromAddress, + from: fromCurrencyCode, + to: toCurrencyCode, + amount: quoteAmount, + affiliateID: initOptions.affiliateId, + affiliateFee: initOptions.affiliateFee.toString() + } + + const swap = await post('swap/create', swapParams) + + if (!swap.success) { + throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) + } + + let swapStatus = { + result: { + deposit_addr_default: '' + } + } + // Poll until error or pending deposit + while (true) { + swapStatus = await get('swap/status?uuid=' + swap.result.uuid) + + if ( + !swapStatus.success || + swapStatus.result.swap_status === 'cancelled' + ) { + throw new SwapCurrencyError( + swapInfo, + fromCurrencyCode, + toCurrencyCode + ) + } + + if (swapStatus.result.swap_status === 'awaitingDeposit') { + break + } + + // Wait for one second + await new Promise(resolve => setTimeout(resolve, 1000)) + } const spendInfo = { currencyCode: request.fromCurrencyCode, spendTargets: [ { - nativeAmount: '0', - publicAddress: '', + nativeAmount: request.nativeAmount, + publicAddress: swapStatus.result.deposit_addr_default, otherParams: { - uniqueIdentifier: '' + uniqueIdentifier: swap.result.uuid } } ] } + io.console.info('CoinZark spendInfo', spendInfo) const tx = await request.fromWallet.makeSpend(spendInfo) tx.otherParams.payinAddress = spendInfo.spendTargets[0].publicAddress tx.otherParams.uniqueIdentifier = spendInfo.spendTargets[0].otherParams.uniqueIdentifier - // Convert that to the output format: return makeSwapPluginQuote( request, - '', - '', + request.nativeAmount, + receiveAmount, tx, - '', - 'CoinZark', - new Date(), - '' + toAddress, + 'coinzark', + new Date(Date.now() + expirationMs), + swap.result.uuid ) } } From bcda662986a8a820f31640fcced3142fbfa083cb Mon Sep 17 00:00:00 2001 From: Gert-Jaap Glasbergen Date: Thu, 2 May 2019 12:49:49 +0200 Subject: [PATCH 3/4] Cleanup and comments --- src/swap/coinzark.js | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/swap/coinzark.js b/src/swap/coinzark.js index 7c36251b..42b0a393 100644 --- a/src/swap/coinzark.js +++ b/src/swap/coinzark.js @@ -73,17 +73,20 @@ export function makeCoinZarkPlugin ( getAddress(toWallet, toCurrencyCode) ]) + // Convert amount to CoinZark supported format const quoteAmount = await fromWallet.nativeToDenomination( nativeAmount, fromCurrencyCode ) + // Convenience function to get JSON from the API async function get (path: string) { const api = `${uri}${path}` const reply = await fetchJson(api) return reply.json } + // Convenience function to post form values and get returned JSON from the API async function post (url, values: any) { const opts = { headers: { @@ -104,16 +107,17 @@ export function makeCoinZarkPlugin ( return out } + // Fetch the supported currencies const currencies = await get('swap/currencies') let fromCorrect = false let toCorrect = false + + // Loop through the currencies and find the requested ones. + // CoinZark will return canDeposit / canReceive as status of the + // coins. The coin we want to exchange from should have canDeposit enabled + // and the coin we want to exchange to should have canReceive enabled. if (!(currencies === null || currencies.result === null)) { for (const curr of currencies.result) { - io.console.info( - `curr.id [${curr.id}] - curr.canDeposit [${ - curr.canDeposit - }] - curr.canReceive [${curr.canReceive}]` - ) if (curr.id === fromCurrencyCode && curr.canDeposit === 1) { fromCorrect = true } @@ -124,10 +128,13 @@ export function makeCoinZarkPlugin ( } } + // Check if we managed to match the requested coin types + // and that they are properly available. If not return an error. if (!fromCorrect || !toCorrect) { throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) } + // Fetch the rate from CoinZark. This also includes the limits. const swapRate = await get( 'swap/rate?from=' + fromCurrencyCode + @@ -151,6 +158,7 @@ export function makeCoinZarkPlugin ( fromCurrencyCode ) + // If the final amount is 0, there is something wrong. Probably the limits. if (swapRate.result.finalAmount === 0) { if ( parseFloat(swapRate.result.depositAmount) < @@ -169,11 +177,13 @@ export function makeCoinZarkPlugin ( throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) } + // Convert the receive amount to native const receiveAmount = await fromWallet.denominationToNative( swapRate.result.finalAmount, fromCurrencyCode ) + // Configure the form parameters for the Swap create call const swapParams = { destination: toAddress, refund: fromAddress, @@ -184,8 +194,10 @@ export function makeCoinZarkPlugin ( affiliateFee: initOptions.affiliateFee.toString() } + // Create the swap const swap = await post('swap/create', swapParams) + // Check if the creation was succesful, otherwise return an error if (!swap.success) { throw new SwapCurrencyError(swapInfo, fromCurrencyCode, toCurrencyCode) } @@ -195,7 +207,9 @@ export function makeCoinZarkPlugin ( deposit_addr_default: '' } } - // Poll until error or pending deposit + + // Poll the status until there's an error or the swap is + // awaiting the deposit while (true) { swapStatus = await get('swap/status?uuid=' + swap.result.uuid) @@ -231,12 +245,16 @@ export function makeCoinZarkPlugin ( ] } - io.console.info('CoinZark spendInfo', spendInfo) + io.console.info('CoinZark spendinfo:', spendInfo) + + // Build the transaction the user has to approve to + // initiate the swap const tx = await request.fromWallet.makeSpend(spendInfo) tx.otherParams.payinAddress = spendInfo.spendTargets[0].publicAddress tx.otherParams.uniqueIdentifier = spendInfo.spendTargets[0].otherParams.uniqueIdentifier + // Return the quote to the user for execution return makeSwapPluginQuote( request, request.nativeAmount, From 4543adef978b4834e9a2f30caa5cd10100b268c7 Mon Sep 17 00:00:00 2001 From: Gert-Jaap Glasbergen Date: Mon, 6 May 2019 12:13:40 +0200 Subject: [PATCH 4/4] Fix styling remark --- src/swap/coinzark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/swap/coinzark.js b/src/swap/coinzark.js index 42b0a393..296f0bc3 100644 --- a/src/swap/coinzark.js +++ b/src/swap/coinzark.js @@ -116,7 +116,7 @@ export function makeCoinZarkPlugin ( // CoinZark will return canDeposit / canReceive as status of the // coins. The coin we want to exchange from should have canDeposit enabled // and the coin we want to exchange to should have canReceive enabled. - if (!(currencies === null || currencies.result === null)) { + if (currencies != null && currencies.result != null) { for (const curr of currencies.result) { if (curr.id === fromCurrencyCode && curr.canDeposit === 1) { fromCorrect = true