Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 39 additions & 14 deletions packages/swap/src/providers/changelly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,10 @@ class Changelly extends ProviderClass {
}

// Can currency can be swapped to?
if (cur.enabledTo && cur.fixRateEnabled && cur.token) {
if (
(cur.enabledTo && (cur.fixRateEnabled || cur.protocol === "RBTC") && cur.token)
) {
// Allowing RBTC and using floating rate
if (!this.toTokens[changellyToNetwork[cur.blockchain]])
this.toTokens[changellyToNetwork[cur.blockchain]] = {};
this.toTokens[changellyToNetwork[cur.blockchain]][cur.token.address] = {
Expand Down Expand Up @@ -290,7 +293,7 @@ class Changelly extends ProviderClass {
}

async getMinMaxAmount(
options: { fromToken: TokenType; toToken: TokenTypeTo },
options: { fromToken: TokenType; toToken: TokenTypeTo; amount?: string },
context?: { signal?: AbortSignal },
): Promise<MinMaxResponse> {
const { fromToken, toToken } = options;
Expand All @@ -305,25 +308,32 @@ class Changelly extends ProviderClass {
};

try {
const params: ChangellyApiGetFixRateParams = {
const params: ChangellyApiGetFixRateParams & { amountFrom?: string } = {
// Optional amountFrom field only used in getExchangeAmount rpc call for floating rates
from: this.getTicker(fromToken, this.network),
to: this.getTicker(
toToken as TokenType,
toToken.networkInfo.name as SupportedNetworkName,
),
};

let method: string = "getFixRate";
if ([params.from, params.to].includes("rbtc")) {
// In case of RBTC use floating rate
method = "getExchangeAmount";
params.amountFrom = options.amount; // amountFrom required only in case of getExchangeAmount
}
const response =
await this.changellyRequest<ChangellyApiGetFixRateResult>(
"getFixRate",
method,
params,
{ signal },
);

if (response.error) {
// JsonRPC ERR response
console.warn(
`Changelly "getFixRate" returned JSONRPC error response` +
`Changelly "${method}" returned JSONRPC error response` +
` fromToken=${fromToken.symbol} (${params.from})` +
` toToken=${toToken.symbol} (${params.to})` +
` took=${(Date.now() - startedAt).toLocaleString()}ms` +
Expand Down Expand Up @@ -422,10 +432,14 @@ class Changelly extends ProviderClass {
);
return null;
}

const amount = fromBase(
options.amount.toString(),
options.fromToken.decimals,
);
const minMax = await this.getMinMaxAmount({
fromToken: options.fromToken,
toToken: options.toToken,
amount: amount,
});

let quoteRequestAmount = options.amount;
Expand Down Expand Up @@ -462,9 +476,13 @@ class Changelly extends ProviderClass {
),
};

let method: string = "getFixRateForAmount";
if ([params.from, params.to].includes("rbtc")) {
method = "getExchangeAmount";
}
const response =
await this.changellyRequest<ChangellyApiGetFixRateForAmountResult>(
"getFixRateForAmount",
method,
params,
Comment on lines +479 to 486
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Don’t inject JSON‑RPC id as a rate id; make quote flow conditional by method.

  • JSON‑RPC id is just a client correlation id; it is not a quote/rate id. Remove the mutation of response.result[0].id = response.id. (docs.changelly.com)
  • Floating estimates (getExchangeAmount) don’t return/use rateId; fixed quotes do. Only require id and propagate changellyQuoteId for the fixed flow (getFixRateForAmount). (docs.changelly.com)
  • Make the RBTC check case‑insensitive and update hardcoded log strings to use the actual method.

Apply:

@@
-      let method: string = "getFixRateForAmount";
-      if ([params.from, params.to].includes("rbtc")) {
-        method = "getExchangeAmount";
-      }
+      const isFloating = [params.from, params.to]
+        .some((t) => t?.toLowerCase() === "rbtc");
+      const method: "getFixRateForAmount" | "getExchangeAmount" =
+        isFloating ? "getExchangeAmount" : "getFixRateForAmount";
@@
-      if (response.error) {
+      if (response.error) {
         console.warn(
-          `Changelly "${method}" returned JSONRPC error response,` +
+          `Changelly "${method}" returned JSONRPC error response,` +
           ` returning no quotes` +
@@
-      if (response.result[0] && response.id) {
-        // getExchangeAmount returns id in response object
-        response.result[0].id = String(response.id);
-      }
-      if (!response.result || !response.result[0]?.id) {
+      if (
+        !response.result ||
+        !response.result[0] ||
+        (method === "getFixRateForAmount" && !response.result[0].id)
+      ) {
         console.warn(
-          `Changelly "${method}" response contains no quotes,` +
+          `Changelly "${method}" response contains no quotes,` +
           ` returning no quotes` +
@@
-        console.warn(
-          `Changelly "getFixRateForAmount" returned more than one quote, continuing with first quote` +
+        console.warn(
+          `Changelly "${method}" returned more than one quote, continuing with first quote` +
           `  fromToken=${options.fromToken.symbol} (${params.from})` +
           `  toToken=${options.toToken.symbol} (${params.to})` +
           `  took=${(Date.now() - startedAt).toLocaleString()}ms` +
           `  count=${response.result.length}ms`,
         );
@@
-        console.warn(
-          `Changelly "getFixRateForAmount" "amountTo" possibly returned more` +
+        console.warn(
+          `Changelly "${method}" "amountTo" possibly returned more` +
           ` decimals than the token has, attempting to trim trailing decimals...` +
@@
-        console.warn(
-          `Changelly "getFixRateForAmount" "networkFee" possibly returned more` +
+        console.warn(
+          `Changelly "${method}" "networkFee" possibly returned more` +
           ` decimals than the token has, attempting to trim trailing decimals...` +
@@
-          meta: {
-            ...meta,
-            changellyQuoteId: firstChangellyFixRateQuote.id,
-            changellynetworkFee: toBN(networkFeeBase),
-          },
+          meta: {
+            ...meta,
+            // Only fixed-rate quotes have a usable rateId
+            changellyQuoteId: method === "getFixRateForAmount"
+              ? firstChangellyFixRateQuote.id
+              : undefined,
+            changellynetworkFee: toBN(networkFeeBase),
+          },

This matches the API: floating uses getExchangeAmount (no rateId), fixed uses getFixRateForAmount (has id for rateId). Also, always show user estimate net of networkFee (you already do). (docs.changelly.com)

Also applies to: 504-507, 508-519, 524-530, 549-556, 585-591, 611-621

🤖 Prompt for AI Agents
In packages/swap/src/providers/changelly/index.ts around lines 479-486 (also
apply same fix patterns at 504-507, 508-519, 524-530, 549-556, 585-591,
611-621): remove the code that mutates response.result[0].id = response.id
(JSON‑RPC id must not be used as a quote id); only set/propagate
changellyQuoteId when the chosen method is "getFixRateForAmount" and use the id
supplied in the API result (e.g., response.result[0].id) if present — do not use
response.id; make the RBTC check case‑insensitive (compare params.from/params.to
after toLowerCase()), and update any hardcoded log messages to include the
actual method variable so logs show which API ("getExchangeAmount" vs
"getFixRateForAmount") was called; ensure the floating flow
("getExchangeAmount") does not expect or require a rateId.

{ signal },
);
Expand All @@ -473,7 +491,7 @@ class Changelly extends ProviderClass {

if (response.error) {
console.warn(
`Changelly "getFixRateForAmount" returned JSONRPC error response,` +
`Changelly "${method}" returned JSONRPC error response,` +
` returning no quotes` +
` fromToken=${options.fromToken.symbol} (${params.from})` +
` toToken=${options.toToken.symbol} (${params.to})` +
Expand All @@ -483,10 +501,13 @@ class Changelly extends ProviderClass {
);
return null;
}

if (response.result[0] && response.id) {
// getExchangeAmount returns id in response object
response.result[0].id = String(response.id);
}
if (!response.result || !response.result[0]?.id) {
console.warn(
`Changelly "getFixRateForAmount" response contains no quotes,` +
`Changelly "${method}" response contains no quotes,` +
` returning no quotes` +
` fromToken=${options.fromToken.symbol} (${params.from})` +
` toToken=${options.toToken.symbol} (${params.to})` +
Expand Down Expand Up @@ -670,17 +691,21 @@ class Changelly extends ProviderClass {
),
rateId: quote.meta.changellyQuoteId,
};


let method: string = "createFixTransaction";
if ([params.from, params.to].includes("rbtc")) {
method = "createTransaction"; // floating rate tx
}
const response =
await this.changellyRequest<ChangellyApiCreateFixedRateTransactionResult>(
"createFixTransaction",
method,
params,
{ signal },
);

if (response.error) {
console.warn(
`Changelly "createFixTransaction" returned JSONRPC error response, returning no swap` +
`Changelly "${method}" returned JSONRPC error response, returning no swap` +
` fromToken=${quote.options.fromToken.symbol} (${params.from})` +
` toToken=${quote.options.toToken.symbol} (${params.to})` +
` took=${(Date.now() - startedAt).toLocaleString()}ms` +
Expand All @@ -692,7 +717,7 @@ class Changelly extends ProviderClass {

if (!response.result.id) {
console.warn(
`Changelly "createFixTransaction" response contains no id, returning no swap` +
`Changelly "${method}" response contains no id, returning no swap` +
` fromToken=${quote.options.fromToken.symbol} (${params.from})` +
` toToken=${quote.options.toToken.symbol} (${params.to})` +
` took=${(Date.now() - startedAt).toLocaleString()}ms`,
Expand Down
Loading