The Strike API /rates/ticker endpoint returns all currency pairs in a single response, not individual pairs. Updated the implementation to: - Remove incorrect currency pair parameter from endpoint URL - Fetch all tickers and filter to requested pair client-side - Add "All Currency Pairs" option to get all rates at once - Add BTC/AUD and BTC/USDT currency pair options - Improve error handling when requested pair not found Fixes 404 "Resource not found" error when using Rates > Get Ticker operation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
552 lines
19 KiB
TypeScript
552 lines
19 KiB
TypeScript
import type {
|
|
IExecuteFunctions,
|
|
IDataObject,
|
|
INodeExecutionData,
|
|
INodeType,
|
|
INodeTypeDescription,
|
|
} from 'n8n-workflow';
|
|
|
|
import { strikeApiRequest, strikeApiRequestAllItems } from './StrikeApi';
|
|
|
|
import {
|
|
balanceOperations,
|
|
balanceFields,
|
|
invoiceOperations,
|
|
invoiceFields,
|
|
paymentOperations,
|
|
paymentFields,
|
|
depositOperations,
|
|
depositFields,
|
|
payoutOperations,
|
|
payoutFields,
|
|
currencyExchangeOperations,
|
|
currencyExchangeFields,
|
|
ratesOperations,
|
|
ratesFields,
|
|
accountOperations,
|
|
accountFields,
|
|
paymentMethodOperations,
|
|
paymentMethodFields,
|
|
eventOperations,
|
|
eventFields,
|
|
} from './descriptions';
|
|
|
|
export class Strike implements INodeType {
|
|
description: INodeTypeDescription = {
|
|
displayName: 'Strike',
|
|
name: 'strike',
|
|
icon: 'file:strike.svg',
|
|
group: ['transform'],
|
|
version: 1,
|
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
|
description: 'Interact with Strike API for Bitcoin and Lightning payments',
|
|
defaults: {
|
|
name: 'Strike',
|
|
},
|
|
inputs: ['main'],
|
|
outputs: ['main'],
|
|
credentials: [
|
|
{
|
|
name: 'strikeApi',
|
|
required: true,
|
|
},
|
|
],
|
|
properties: [
|
|
{
|
|
displayName: 'Resource',
|
|
name: 'resource',
|
|
type: 'options',
|
|
noDataExpression: true,
|
|
options: [
|
|
{
|
|
name: 'Account',
|
|
value: 'account',
|
|
},
|
|
{
|
|
name: 'Balance',
|
|
value: 'balance',
|
|
},
|
|
{
|
|
name: 'Currency Exchange',
|
|
value: 'currencyExchange',
|
|
},
|
|
{
|
|
name: 'Deposit',
|
|
value: 'deposit',
|
|
},
|
|
{
|
|
name: 'Event',
|
|
value: 'event',
|
|
},
|
|
{
|
|
name: 'Invoice',
|
|
value: 'invoice',
|
|
},
|
|
{
|
|
name: 'Payment',
|
|
value: 'payment',
|
|
},
|
|
{
|
|
name: 'Payment Method',
|
|
value: 'paymentMethod',
|
|
},
|
|
{
|
|
name: 'Payout',
|
|
value: 'payout',
|
|
},
|
|
{
|
|
name: 'Rates',
|
|
value: 'rates',
|
|
},
|
|
],
|
|
default: 'balance',
|
|
},
|
|
// Operations and fields for each resource
|
|
...accountOperations,
|
|
...accountFields,
|
|
...balanceOperations,
|
|
...balanceFields,
|
|
...currencyExchangeOperations,
|
|
...currencyExchangeFields,
|
|
...depositOperations,
|
|
...depositFields,
|
|
...eventOperations,
|
|
...eventFields,
|
|
...invoiceOperations,
|
|
...invoiceFields,
|
|
...paymentOperations,
|
|
...paymentFields,
|
|
...paymentMethodOperations,
|
|
...paymentMethodFields,
|
|
...payoutOperations,
|
|
...payoutFields,
|
|
...ratesOperations,
|
|
...ratesFields,
|
|
],
|
|
};
|
|
|
|
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
|
const items = this.getInputData();
|
|
const returnData: INodeExecutionData[] = [];
|
|
const resource = this.getNodeParameter('resource', 0) as string;
|
|
const operation = this.getNodeParameter('operation', 0) as string;
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
try {
|
|
let responseData: any;
|
|
|
|
// ----------------------------------------
|
|
// account
|
|
// ----------------------------------------
|
|
if (resource === 'account') {
|
|
if (operation === 'getProfile') {
|
|
const accountId = this.getNodeParameter('accountId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/accounts/${accountId}/profile`);
|
|
} else if (operation === 'getProfileByHandle') {
|
|
const handle = this.getNodeParameter('handle', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/accounts/handle/${handle}/profile`);
|
|
} else if (operation === 'getLimits') {
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/accounts/limits');
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// balance
|
|
// ----------------------------------------
|
|
if (resource === 'balance') {
|
|
if (operation === 'get') {
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/balances');
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// currencyExchange
|
|
// ----------------------------------------
|
|
if (resource === 'currencyExchange') {
|
|
if (operation === 'createQuote') {
|
|
const sell = this.getNodeParameter('sell', i) as string;
|
|
const buy = this.getNodeParameter('buy', i) as string;
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const amountCurrency = this.getNodeParameter('amountCurrency', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
sell,
|
|
buy,
|
|
amount: {
|
|
amount: amount.toString(),
|
|
currency: amountCurrency === 'sell' ? sell : buy,
|
|
},
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/currency-exchange-quotes', body);
|
|
} else if (operation === 'getQuote') {
|
|
const quoteId = this.getNodeParameter('quoteId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/currency-exchange-quotes/${quoteId}`);
|
|
} else if (operation === 'executeQuote') {
|
|
const quoteId = this.getNodeParameter('quoteId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'PATCH', `/currency-exchange-quotes/${quoteId}/execute`);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// event
|
|
// ----------------------------------------
|
|
if (resource === 'event') {
|
|
if (operation === 'get') {
|
|
const eventId = this.getNodeParameter('eventId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/events/${eventId}`);
|
|
} else if (operation === 'getAll') {
|
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
|
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
|
const query: IDataObject = {};
|
|
|
|
if (filters.filter) {
|
|
query.$filter = filters.filter;
|
|
}
|
|
if (filters.orderBy) {
|
|
query.$orderby = filters.orderBy;
|
|
}
|
|
|
|
if (returnAll) {
|
|
responseData = await strikeApiRequestAllItems.call(this, 'GET', '/events', {}, query);
|
|
} else {
|
|
const limit = this.getNodeParameter('limit', i) as number;
|
|
query.$top = limit;
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/events', {}, query);
|
|
responseData = responseData.items || responseData;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// deposit
|
|
// ----------------------------------------
|
|
if (resource === 'deposit') {
|
|
if (operation === 'create') {
|
|
const paymentMethodId = this.getNodeParameter('paymentMethodId', i) as string;
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const currency = this.getNodeParameter('currency', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
paymentMethodId,
|
|
amount: {
|
|
amount: amount.toString(),
|
|
currency,
|
|
},
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/deposits', body);
|
|
} else if (operation === 'get') {
|
|
const depositId = this.getNodeParameter('depositId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/deposits/${depositId}`);
|
|
} else if (operation === 'getAll') {
|
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
|
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
|
const query: IDataObject = {};
|
|
|
|
if (filters.filter) {
|
|
query.$filter = filters.filter;
|
|
}
|
|
if (filters.orderBy) {
|
|
query.$orderby = filters.orderBy;
|
|
}
|
|
|
|
if (returnAll) {
|
|
responseData = await strikeApiRequestAllItems.call(this, 'GET', '/deposits', {}, query);
|
|
} else {
|
|
const limit = this.getNodeParameter('limit', i) as number;
|
|
query.$top = limit;
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/deposits', {}, query);
|
|
responseData = responseData.items || responseData;
|
|
}
|
|
} else if (operation === 'estimateFee') {
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const currency = this.getNodeParameter('currency', i) as string;
|
|
const feePolicy = this.getNodeParameter('feePolicy', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
amount: {
|
|
amount: amount.toString(),
|
|
currency,
|
|
},
|
|
feePolicy,
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/deposits/fee', body);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// invoice
|
|
// ----------------------------------------
|
|
if (resource === 'invoice') {
|
|
if (operation === 'create') {
|
|
const correlationId = this.getNodeParameter('correlationId', i) as string;
|
|
const description = this.getNodeParameter('description', i) as string;
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const currency = this.getNodeParameter('currency', i) as string;
|
|
|
|
const body: IDataObject = {};
|
|
|
|
if (correlationId) {
|
|
body.correlationId = correlationId;
|
|
}
|
|
if (description) {
|
|
body.description = description;
|
|
}
|
|
if (amount > 0) {
|
|
body.amount = {
|
|
amount: amount.toString(),
|
|
currency,
|
|
};
|
|
}
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/invoices', body);
|
|
} else if (operation === 'get') {
|
|
const invoiceId = this.getNodeParameter('invoiceId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/invoices/${invoiceId}`);
|
|
} else if (operation === 'getAll') {
|
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
|
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
|
const query: IDataObject = {};
|
|
|
|
if (filters.filter) {
|
|
query.$filter = filters.filter;
|
|
}
|
|
if (filters.orderBy) {
|
|
query.$orderby = filters.orderBy;
|
|
}
|
|
|
|
if (returnAll) {
|
|
responseData = await strikeApiRequestAllItems.call(this, 'GET', '/invoices', {}, query);
|
|
} else {
|
|
const limit = this.getNodeParameter('limit', i) as number;
|
|
query.$top = limit;
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/invoices', {}, query);
|
|
responseData = responseData.items || responseData;
|
|
}
|
|
} else if (operation === 'createQuote') {
|
|
const invoiceId = this.getNodeParameter('invoiceId', i) as string;
|
|
const descriptionHash = this.getNodeParameter('descriptionHash', i) as string;
|
|
|
|
const body: IDataObject = {};
|
|
if (descriptionHash) {
|
|
body.descriptionHash = descriptionHash;
|
|
}
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', `/invoices/${invoiceId}/quote`, body);
|
|
} else if (operation === 'cancel') {
|
|
const invoiceId = this.getNodeParameter('invoiceId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'PATCH', `/invoices/${invoiceId}/cancel`);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// payment
|
|
// ----------------------------------------
|
|
if (resource === 'payment') {
|
|
if (operation === 'get') {
|
|
const paymentId = this.getNodeParameter('paymentId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/payments/${paymentId}`);
|
|
} else if (operation === 'createLightningQuote') {
|
|
const lnInvoice = this.getNodeParameter('lnInvoice', i) as string;
|
|
const sourceCurrency = this.getNodeParameter('sourceCurrency', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
lnInvoice,
|
|
sourceCurrency,
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/payment-quotes/lightning', body);
|
|
} else if (operation === 'createOnchainQuote') {
|
|
const btcAddress = this.getNodeParameter('btcAddress', i) as string;
|
|
const sourceCurrency = this.getNodeParameter('sourceCurrency', i) as string;
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const amountCurrency = this.getNodeParameter('amountCurrency', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
btcAddress,
|
|
sourceCurrency,
|
|
amount: {
|
|
amount: amount.toString(),
|
|
currency: amountCurrency,
|
|
},
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/payment-quotes/onchain', body);
|
|
} else if (operation === 'createLnurlQuote') {
|
|
const lnAddressOrUrl = this.getNodeParameter('lnAddressOrUrl', i) as string;
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const amountCurrency = this.getNodeParameter('amountCurrency', i) as string;
|
|
const sourceCurrency = this.getNodeParameter('sourceCurrency', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
lnAddressOrUrl,
|
|
sourceCurrency,
|
|
amount: {
|
|
amount: amount.toString(),
|
|
currency: amountCurrency,
|
|
},
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/payment-quotes/lightning/lnurl', body);
|
|
} else if (operation === 'getLnurlDetails') {
|
|
const lnAddressOrUrl = this.getNodeParameter('lnAddressOrUrl', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/payment-quotes/lightning/lnurl/${encodeURIComponent(lnAddressOrUrl)}`);
|
|
} else if (operation === 'executeQuote') {
|
|
const paymentQuoteId = this.getNodeParameter('paymentQuoteId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'PATCH', `/payment-quotes/${paymentQuoteId}/execute`);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// paymentMethod
|
|
// ----------------------------------------
|
|
if (resource === 'paymentMethod') {
|
|
if (operation === 'createBank') {
|
|
const accountNumber = this.getNodeParameter('accountNumber', i) as string;
|
|
const routingNumber = this.getNodeParameter('routingNumber', i) as string;
|
|
const accountType = this.getNodeParameter('accountType', i) as string;
|
|
|
|
const body: IDataObject = {
|
|
accountNumber,
|
|
routingNumber,
|
|
accountType,
|
|
};
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/payment-methods/bank', body);
|
|
} else if (operation === 'get') {
|
|
const paymentMethodId = this.getNodeParameter('paymentMethodId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/payment-methods/bank/${paymentMethodId}`);
|
|
} else if (operation === 'getAll') {
|
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
|
const options = this.getNodeParameter('options', i) as IDataObject;
|
|
const query: IDataObject = {};
|
|
|
|
if (options.depositEligible) {
|
|
query.depositEligible = true;
|
|
}
|
|
|
|
if (returnAll) {
|
|
responseData = await strikeApiRequestAllItems.call(this, 'GET', '/payment-methods/bank', {}, query);
|
|
} else {
|
|
const limit = this.getNodeParameter('limit', i) as number;
|
|
query.$top = limit;
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/payment-methods/bank', {}, query);
|
|
responseData = responseData.items || responseData;
|
|
}
|
|
} else if (operation === 'delete') {
|
|
const paymentMethodId = this.getNodeParameter('paymentMethodId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'DELETE', `/payment-methods/bank/${paymentMethodId}`);
|
|
responseData = { success: true };
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// payout
|
|
// ----------------------------------------
|
|
if (resource === 'payout') {
|
|
if (operation === 'create') {
|
|
const paymentMethodId = this.getNodeParameter('paymentMethodId', i) as string;
|
|
const amount = this.getNodeParameter('amount', i) as number;
|
|
const currency = this.getNodeParameter('currency', i) as string;
|
|
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
|
|
|
|
const body: IDataObject = {
|
|
paymentMethodId,
|
|
amount: {
|
|
amount: amount.toString(),
|
|
currency,
|
|
},
|
|
};
|
|
|
|
if (additionalFields.feePolicy) {
|
|
body.feePolicy = additionalFields.feePolicy;
|
|
}
|
|
if (additionalFields.originatorId) {
|
|
body.originatorId = additionalFields.originatorId;
|
|
}
|
|
|
|
responseData = await strikeApiRequest.call(this, 'POST', '/payouts', body);
|
|
} else if (operation === 'get') {
|
|
const payoutId = this.getNodeParameter('payoutId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'GET', `/payouts/${payoutId}`);
|
|
} else if (operation === 'getAll') {
|
|
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
|
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
|
const query: IDataObject = {};
|
|
|
|
if (filters.filter) {
|
|
query.$filter = filters.filter;
|
|
}
|
|
if (filters.orderBy) {
|
|
query.$orderby = filters.orderBy;
|
|
}
|
|
|
|
if (returnAll) {
|
|
responseData = await strikeApiRequestAllItems.call(this, 'GET', '/payouts', {}, query);
|
|
} else {
|
|
const limit = this.getNodeParameter('limit', i) as number;
|
|
query.$top = limit;
|
|
responseData = await strikeApiRequest.call(this, 'GET', '/payouts', {}, query);
|
|
responseData = responseData.items || responseData;
|
|
}
|
|
} else if (operation === 'initiate') {
|
|
const payoutId = this.getNodeParameter('payoutId', i) as string;
|
|
responseData = await strikeApiRequest.call(this, 'PATCH', `/payouts/${payoutId}/initiate`);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------
|
|
// rates
|
|
// ----------------------------------------
|
|
if (resource === 'rates') {
|
|
if (operation === 'getTicker') {
|
|
const currencyPair = this.getNodeParameter('currencyPair', i) as string;
|
|
// The Strike API returns all currency pairs
|
|
const allTickers = await strikeApiRequest.call(this, 'GET', '/rates/ticker');
|
|
|
|
// Filter to the requested currency pair
|
|
if (currencyPair && currencyPair !== 'all') {
|
|
// Extract source and target currencies (e.g., 'BTCUSD' -> 'BTC' and 'USD')
|
|
const sourceCurrency = currencyPair.slice(0, 3);
|
|
const targetCurrency = currencyPair.slice(3);
|
|
|
|
responseData = allTickers.find((ticker: any) =>
|
|
ticker.sourceCurrency === sourceCurrency &&
|
|
ticker.targetCurrency === targetCurrency
|
|
);
|
|
|
|
if (!responseData) {
|
|
responseData = { error: `Currency pair ${currencyPair} not found in available tickers` };
|
|
}
|
|
} else {
|
|
// Return all tickers if 'all' is selected
|
|
responseData = allTickers;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return data
|
|
const executionData = this.helpers.constructExecutionMetaData(
|
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
|
{ itemData: { item: i } },
|
|
);
|
|
returnData.push(...executionData);
|
|
} catch (error) {
|
|
if (this.continueOnFail()) {
|
|
const executionData = this.helpers.constructExecutionMetaData(
|
|
this.helpers.returnJsonArray({ error: (error as Error).message }),
|
|
{ itemData: { item: i } },
|
|
);
|
|
returnData.push(...executionData);
|
|
continue;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
return [returnData];
|
|
}
|
|
}
|