Enable RBTC swaps using Changelly floating point API#767
Enable RBTC swaps using Changelly floating point API#767kvhnuke merged 1 commit intoenkryptcom:devop/package-updates-2-14from
RBTC swaps using Changelly floating point API#767Conversation
WalkthroughChange updates the Changelly swap provider to accept an optional Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant ChangellyProvider
participant ChangellyRPC
Client->>ChangellyProvider: getQuote(fromToken, toToken, amount)
alt RBTC involved (floating-rate)
ChangellyProvider->>ChangellyRPC: getExchangeAmount(params with amountFrom)
ChangellyRPC-->>ChangellyProvider: exchangeAmountResponse (id, amount)
ChangellyProvider->>ChangellyRPC: createTransaction(params with amountFrom)
ChangellyRPC-->>ChangellyProvider: transactionResponse (id)
else Fixed-rate path
ChangellyProvider->>ChangellyRPC: getFixRateForAmount(params)
ChangellyRPC-->>ChangellyProvider: fixRateResponse (id, amount)
ChangellyProvider->>ChangellyRPC: createFixTransaction(params)
ChangellyRPC-->>ChangellyProvider: fixTransactionResponse (id)
end
ChangellyProvider-->>Client: normalizedQuote / transactionResult
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/swap/src/providers/changelly/index.ts (1)
510-517: Avoid accessingresponse.error.*when there is no error (can throw).On the “no quotes” branch,
response.errorcan be undefined. Guard it to prevent runtime exceptions.- ` code=${String(response.error.code)}` + - ` message=${String(response.error.message)}`, + ` code=${String(response.error?.code ?? "N/A")}` + + ` message=${String(response.error?.message ?? "N/A")}`,
🧹 Nitpick comments (2)
packages/swap/src/providers/changelly/index.ts (2)
312-327: Method selection, deprecations, and logging fixes.
- getFixRate is deprecated in v2; prefer getFixRateForAmount for fixed rates. (docs.changelly.com)
- For floating, getExchangeAmount is correct; it can include limits (min/max) when
amountFromis within limits. If out of limits, consider a fallback (temporary) to deprecated getMinAmount or retry heuristics. (docs.changelly.com)- Normalize ticker case when checking for RBTC.
- Hoist
methodso the catch can log the actual method.@@ - try { - const params: ChangellyApiGetFixRateParams & { amountFrom?: string } = { + // Default to fixed-rate method; may be overridden below. + let method: "getFixRate" | "getExchangeAmount" = "getFixRate"; + try { + 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")) { + // Use floating rate for RBTC pairs + if ([params.from, params.to].map((t) => t?.toLowerCase()).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>( - method, - params, - { signal }, - ); + const response = + await this.changellyRequest< + ChangellyApiGetFixRateResult | ChangellyApiGetFixRateForAmountResult + >(method, params, { signal }); @@ - console.warn( - `Errored calling Changelly JSONRPC HTTP API "getFixRate"` + + console.warn( + `Errored calling Changelly JSONRPC HTTP API "${method}"` + ` fromToken=${fromToken.symbol}` + ` toToken=${toToken.symbol}` + ` took=${(Date.now() - startedAt).toLocaleString()}ms` + ` err=${String(err)}`, );Note: getFixRate is deprecated; switching to getFixRateForAmount aligns with v2 guidance. (docs.changelly.com)
Also applies to: 365-371
321-326: Normalize RBTC ticker check to lowercase to avoid case‑sensitivity bugs.Use
.toLowerCase()when checking for"rbtc"in both places.- if ([params.from, params.to].includes("rbtc")) { + if ([params.from, params.to].map((t) => t?.toLowerCase()).includes("rbtc")) { @@ - if ([params.from, params.to].includes("rbtc")) { + if ([params.from, params.to].map((t) => t?.toLowerCase()).includes("rbtc")) {Also applies to: 480-482
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/swap/src/providers/changelly/index.ts(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/swap/src/providers/changelly/index.ts (4)
packages/swap/src/types/index.ts (2)
TokenType(49-60)TokenTypeTo(78-80)packages/swap/src/providers/changelly/types.ts (4)
ChangellyApiGetFixRateParams(138-152)ChangellyApiGetFixRateResult(159-258)ChangellyApiGetFixRateForAmountResult(326-437)ChangellyApiCreateFixedRateTransactionResult(558-686)packages/utils/src/index.ts (1)
fromBase(49-49)packages/utils/src/units.ts (1)
fromBase(159-159)
🔇 Additional comments (1)
packages/swap/src/providers/changelly/index.ts (1)
296-299: Rewrite as: No action required — this is a safe, provider-specific change.The method
getMinMaxAmount()is not defined in the abstractProviderClassinterface (packages/swap/src/types/index.ts:296–319), so the signature change in Changelly is provider-internal, not a breaking public API change. All invocations within Changelly correctly pass the optionalamountparameter, and no external code outside the provider implementations calls this method.Likely an incorrect or invalid review comment.
8152a38 to
159f039
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/swap/src/providers/changelly/index.ts (2)
296-327: Min/max cannot come from getExchangeAmount; use fixed-rate endpoint (or dedicated min/max).getMinMaxAmount switches to getExchangeAmount for RBTC, but later reads minFrom/maxFrom/minTo/maxTo which aren’t provided by getExchangeAmount. This will break RBTC quotes (min/max become undefined → toBase/toBN errors).
Apply:
- 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 method = "getFixRate"; // min/max are exposed by this endpoint even if swap flow is floatingAlso fix the catch log to reflect the actual method:
- `Errored calling Changelly JSONRPC HTTP API "getFixRate"` + + `Errored calling Changelly JSONRPC HTTP API "${method}"` +Also applies to: 347-355, 365-371
510-517: Crash risk: logging references response.error when there is no error.In the “no quotes” branch, response.error is undefined, so response.error.code/message will throw.
- if (!response.result || !response.result[0]?.id) { + if (!response.result || !response.result[0]?.id) { console.warn( - `Changelly "${method}" response contains no quotes,` + - ` returning no quotes` + - ` fromToken=${options.fromToken.symbol} (${params.from})` + - ` toToken=${options.toToken.symbol} (${params.to})` + - ` took=${(Date.now() - startedAt).toLocaleString()}ms` + - ` code=${String(response.error.code)}` + - ` message=${String(response.error.message)}`, + `Changelly "${method}" response contains no quotes,` + + ` returning no quotes` + + ` fromToken=${options.fromToken.symbol} (${params.from})` + + ` toToken=${options.toToken.symbol} (${params.to})` + + ` took=${(Date.now() - startedAt).toLocaleString()}ms`, ); return null; }
♻️ Duplicate comments (4)
packages/swap/src/providers/changelly/index.ts (4)
611-621: Only fixed‑rate quotes have a rateId; don’t set changellyQuoteId for floating.- meta: { - ...meta, - changellyQuoteId: firstChangellyFixRateQuote.id, - changellynetworkFee: toBN(networkFeeBase), - }, + meta: { + ...meta, + // rateId present only for fixed quotes + changellyQuoteId: method === "getFixRateForAmount" ? firstChangellyFixRateQuote.id : undefined, + changellynetworkFee: toBN(networkFeeBase), + },
680-697: Build swap params by flow; omit rateId for floating and include fromAddress.- const params: ChangellyApiCreateFixedRateTransactionParams = { - from: this.getTicker(quote.options.fromToken, this.network), - to: this.getTicker( - quote.options.toToken as TokenType, - quote.options.toToken.networkInfo.name as SupportedNetworkName, - ), - refundAddress: quote.options.fromAddress, - address: quote.options.toAddress, - amountFrom: fromBase( - quote.options.amount.toString(), - quote.options.fromToken.decimals, - ), - rateId: quote.meta.changellyQuoteId, - }; - let method: string = "createFixTransaction"; - if ([params.from, params.to].includes("rbtc")) { - method = "createTransaction"; // floating rate tx - } + const fromTicker = this.getTicker(quote.options.fromToken, this.network); + const toTicker = this.getTicker( + quote.options.toToken as TokenType, + quote.options.toToken.networkInfo.name as SupportedNetworkName, + ); + const isFloating = [fromTicker, toTicker].some((t) => t?.toLowerCase() === "rbtc"); + const baseParams = { + from: fromTicker, + to: toTicker, + refundAddress: quote.options.fromAddress, + address: quote.options.toAddress, + amountFrom: fromBase( + quote.options.amount.toString(), + quote.options.fromToken.decimals, + ), + } as any; + const params: any = isFloating + ? { ...baseParams, fromAddress: quote.options.fromAddress } + : { ...baseParams, rateId: quote.meta.changellyQuoteId }; + const method: "createFixTransaction" | "createTransaction" = + isFloating ? "createTransaction" : "createFixTransaction";
145-152: Guard against missing token in RBTC branch (runtime NPE).cur.protocol === "RBTC" path doesn’t require cur.token, but below you read cur.token.address. Add cur.token to the guard.
Apply:
- if ( - (cur.enabledTo && cur.fixRateEnabled && cur.token) || - cur.protocol === "RBTC" - ) { + if ( + cur.token && + ((cur.enabledTo && cur.fixRateEnabled) || cur.protocol === "RBTC") + ) {
504-507: Don’t treat JSON‑RPC id as a quote/rate id. Remove override.- if (response.result[0] && response.id) { - // getExchangeAmount returns id in response object - response.result[0].id = String(response.id); - }
🧹 Nitpick comments (5)
packages/swap/src/providers/changelly/index.ts (5)
479-482: RBTC flow switch OK; make the check case-insensitive.- let method: string = "getFixRateForAmount"; - if ([params.from, params.to].includes("rbtc")) { + let method: string = "getFixRateForAmount"; + if ([params.from, params.to].some((t) => t?.toLowerCase() === "rbtc")) { method = "getExchangeAmount"; }
521-530: Use dynamic method name in log.- `Changelly "getFixRateForAmount" returned more than one quote, continuing with first quote` + + `Changelly "${method}" returned more than one quote, continuing with first quote` +
633-636: Make success log reflect the actual endpoint.- `getQuote: Successfully retrieved quote from Changelly via "getFixRateForAmount"` + + `getQuote: Successfully retrieved quote from Changelly via "${method}"` +
989-994: Use dynamic method name in error log for amountExpectedTo parsing.- `Changelly "createFixTransaction" "amountExpectedTo" possibly returned more` + + `Changelly "${method}" "amountExpectedTo" possibly returned more` +
479-482: Normalize RBTC checks in all places.Use .toLowerCase() wherever you check for RBTC to avoid case mismatches across lists and tickers.
Also applies to: 695-697
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/swap/src/providers/changelly/index.ts(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/swap/src/providers/changelly/index.ts (3)
packages/swap/src/types/index.ts (2)
TokenType(49-60)TokenTypeTo(78-80)packages/swap/src/providers/changelly/types.ts (4)
ChangellyApiGetFixRateParams(138-152)ChangellyApiGetFixRateResult(159-258)ChangellyApiGetFixRateForAmountResult(326-437)ChangellyApiCreateFixedRateTransactionResult(558-686)packages/utils/src/index.ts (1)
fromBase(49-49)
🔇 Additional comments (2)
packages/swap/src/providers/changelly/index.ts (2)
436-444: Passing normalized amount for min/max context is fine.Confirm that getMinMaxAmount still behaves correctly now that it ignores amount for min/max when using getFixRate.
297-297: Review comment can be resolved — no external call sites are affected.The verification confirms that
getMinMaxAmount()is not part of the abstractProviderClasscontract (absent from type definitions), so signature variations across providers are acceptable. The only call site is internal to Changelly (line 440) and already passes theoptionsparameter, making it compatible with the new optionalamountfield. No external code depends on this method.
159f039 to
3d13b84
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/swap/src/providers/changelly/index.ts (1)
679-699: Build swap params per flow; omitrateIdfor floating and use transaction’snetworkFeefor final amount.
- Floating
createTransactionrequiresamountFromand may includefromAddress; it must not includerateId. FixedcreateFixTransactionrequiresrateId. (docs.changelly.com)- Use the
networkFeereturned by the create‑transaction response (not the earlier quote meta) when computingtoTokenAmount, as fees can change between estimate and creation. (docs.changelly.com)Apply:
@@ - const params: ChangellyApiCreateFixedRateTransactionParams = { - from: this.getTicker(quote.options.fromToken, this.network), - to: this.getTicker( - quote.options.toToken as TokenType, - quote.options.toToken.networkInfo.name as SupportedNetworkName, - ), - refundAddress: quote.options.fromAddress, - address: quote.options.toAddress, - amountFrom: fromBase( - quote.options.amount.toString(), - quote.options.fromToken.decimals, - ), - rateId: quote.meta.changellyQuoteId, - }; - - let method: string = "createFixTransaction"; - if ([params.from, params.to].includes("rbtc")) { - method = "createTransaction"; // floating rate tx - } + const fromTicker = this.getTicker(quote.options.fromToken, this.network); + const toTicker = this.getTicker( + quote.options.toToken as TokenType, + quote.options.toToken.networkInfo.name as SupportedNetworkName, + ); + const isFloating = [fromTicker, toTicker] + .some((t) => t?.toLowerCase() === "rbtc"); + const baseParams = { + from: fromTicker, + to: toTicker, + refundAddress: quote.options.fromAddress, + address: quote.options.toAddress, + amountFrom: fromBase( + quote.options.amount.toString(), + quote.options.fromToken.decimals, + ), + } as any; + const params: any = isFloating + ? { ...baseParams, fromAddress: quote.options.fromAddress } + : { ...baseParams, rateId: quote.meta.changellyQuoteId }; + const method: "createTransaction" | "createFixTransaction" = + isFloating ? "createTransaction" : "createFixTransaction"; @@ - const fee = 1; - let baseToAmount: string; + const fee = 1; + let baseToAmount: string; @@ - const retResponse: ProviderSwapResponse = { + // Use the network fee returned by the create*Transaction response + const txNetworkFeeBase = toBase( + changellyFixedRateTx.networkFee, + quote.options.toToken.decimals, + ); + const retResponse: ProviderSwapResponse = { fromTokenAmount: quote.options.amount, provider: this.name, type: SwapType.regular, - toTokenAmount: toBN(baseToAmount).sub(quote.meta.changellynetworkFee), + toTokenAmount: toBN(baseToAmount).sub(toBN(txNetworkFeeBase)), additionalNativeFees, transactions: [transaction], slippage: quote.meta.slippage || DEFAULT_SLIPPAGE, fee,Also applies to: 701-704, 980-1016, 1017-1026
♻️ Duplicate comments (1)
packages/swap/src/providers/changelly/index.ts (1)
145-161: RBTC guard fix in toTokens looks good.Condition now requires
cur.tokenwhen allowing RBTC as a destination; avoids NPEs when accessingcur.token.address.
🧹 Nitpick comments (2)
packages/swap/src/providers/changelly/index.ts (2)
296-326: Make RBTC detection case‑insensitive and use the selected method consistently in logs.
- Use lowercase when checking for
"rbtc"to avoid case brittleness.- Reuse the selected
methodin the catch log (currently hardcoded"getFixRate"), by hoistingmethodoutsidetry.Apply:
@@ - async getMinMaxAmount( - options: { fromToken: TokenType; toToken: TokenTypeTo; amount?: string }, + async getMinMaxAmount( + options: { fromToken: TokenType; toToken: TokenTypeTo; amount?: string }, context?: { signal?: AbortSignal }, ): Promise<MinMaxResponse> { @@ - try { - const params: ChangellyApiGetFixRateParams & { amountFrom?: string } = { + let method: "getFixRate" | "getExchangeAmount" = "getFixRate"; + try { + 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")) { + const isFloating = [params.from, params.to] + .some((t) => t?.toLowerCase() === "rbtc"); + if (isFloating) { // In case of RBTC use floating rate method = "getExchangeAmount"; - params.amountFrom = options.amount; // amountFrom required only in case of getExchangeAmount + params.amountFrom = options.amount; // required by getExchangeAmount } @@ - console.warn( - `Errored calling Changelly JSONRPC HTTP API "getFixRate"` + + console.warn( + `Errored calling Changelly JSONRPC HTTP API "${method}"` + ` fromToken=${fromToken.symbol}` + ` toToken=${toToken.symbol}` + ` took=${(Date.now() - startedAt).toLocaleString()}ms` + ` err=${String(err)}`, );Also applies to: 333-341, 364-371
333-341: Optional: parse floating error to derive limits or retry with safe amount.If
getExchangeAmountreturns “Invalid amount … minimal/maximal amount is …” you currently return zeros, which suppresses quotes even when a valid clamp is possible. Consider:
- Parsing the error to extract
{limit}and populate min/max, or- Fallback to a second
getExchangeAmountwith a safe probe amount inside limits, or- As a last resort, hit the deprecated limits endpoints to derive min/max.
Ref: limits guidance and deprecations. (docs.changelly.com)
Would you like a follow‑up patch that parses the error and populates min/max automatically?
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/swap/src/providers/changelly/index.ts(9 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/swap/src/providers/changelly/index.ts (4)
packages/swap/src/types/index.ts (2)
TokenType(49-60)TokenTypeTo(78-80)packages/swap/src/providers/changelly/types.ts (3)
ChangellyApiGetFixRateParams(138-152)ChangellyApiGetFixRateForAmountResult(326-437)ChangellyApiCreateFixedRateTransactionResult(558-686)packages/utils/src/index.ts (1)
fromBase(49-49)packages/utils/src/units.ts (1)
fromBase(159-159)
| let method: string = "getFixRateForAmount"; | ||
| if ([params.from, params.to].includes("rbtc")) { | ||
| method = "getExchangeAmount"; | ||
| } | ||
| const response = | ||
| await this.changellyRequest<ChangellyApiGetFixRateForAmountResult>( | ||
| "getFixRateForAmount", | ||
| method, | ||
| params, |
There was a problem hiding this comment.
Don’t inject JSON‑RPC id as a rate id; make quote flow conditional by method.
- JSON‑RPC
idis just a client correlation id; it is not a quote/rate id. Remove the mutation ofresponse.result[0].id = response.id. (docs.changelly.com) - Floating estimates (
getExchangeAmount) don’t return/userateId; fixed quotes do. Only requireidand propagatechangellyQuoteIdfor 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.
5602f3b
into
enkryptcom:devop/package-updates-2-14
Description
This PR enables
RBTCswaps usingchangellyfloating point api.API Reference: https://docs.changelly.com/category/floating-rate-transactions
Video Demo
rbtc-transfer.mp4
Demo screenshots
Testing Status
Tested RBTC swaps between ETH and Rootstock.
Summary by CodeRabbit