Skip to content

Conversation

@gregdhill
Copy link
Contributor

@gregdhill gregdhill commented Sep 8, 2025

Adding a wrapper around Gateway using Swing for BTC <> any chain swaps

Summary by CodeRabbit

  • New Features

    • Added Swing-powered cross-chain transfer capability, enabling users to fetch quotes and initiate transfers between supported networks. Initial rollout is limited to BOB mainnet.
  • Chores

    • Added a new SDK dependency to support the Swing integration.
  • Tests

    • Introduced a long-timeout test to exercise quote retrieval for cross-chain transfers.

Signed-off-by: Gregory Hill <gregorydhill@outlook.com>
@vercel
Copy link

vercel bot commented Sep 8, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
bob-docs Ready Ready Preview Comment Sep 8, 2025 3:13pm

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Summary of Changes

Hello @gregdhill, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces the integration of the Swing SDK to facilitate cross-chain token swaps, particularly for BTC <> any chain swaps, by wrapping the functionality within a new client that extends the existing Gateway framework. The primary goal is to enable seamless asset movement between different blockchain networks, starting with support for the BOB mainnet.

Highlights

  • New Dependency: The @swing.xyz/sdk has been added to the sdk/package.json dependencies, enabling integration with the Swing cross-chain protocol.
  • Swing Client Implementation: A new SwingClient class has been introduced in sdk/src/gateway/swing.ts. This client is responsible for interacting with the Swing API to fetch cross-chain quotes and execute token transfers.
  • Gateway Integration: The SwingGatewayClient has been added, extending the GatewayApiClient. This client leverages the SwingClient to provide cross-chain swap capabilities within the Gateway, currently supporting only the BOB mainnet.
  • Unit Tests: A new test file, sdk/test/swing.test.ts, has been added to include unit tests for the SwingClient, specifically verifying its ability to retrieve quotes.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 8, 2025

Walkthrough

Added Swing SDK dependency. Introduced Swing-based gateway with quote and send flow restricted to BOB mainnet. Implemented SwingClient to call Swing cross-chain quote and send endpoints. Added a Vitest test that invokes getQuote with sample parameters and logs the response.

Changes

Cohort / File(s) Summary
Dependencies
sdk/package.json
Added dependency @swing.xyz/sdk (^0.67.7). Reordered existing global and viem entries.
Gateway: Swing integration
sdk/src/gateway/swing.ts
New SwingClient wrapping SwingSDK to GET quote (/v0/transfer/quote) and POST send (/v0/transfer/send). Added SwingGatewayClient extending GatewayApiClient; enforces chainId === bob.id. Uses zeroAddress for user addresses.
Tests
sdk/test/swing.test.ts
New Vitest test suite invoking SwingClient.getQuote with BOB→Base USDC parameters; logs result; 120s timeout; no assertions.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as Caller
  participant GW as SwingGatewayClient
  participant SC as SwingClient
  participant SDK as SwingSDK
  participant API as Swing Cross-Chain API

  Dev->>GW: new SwingGatewayClient(chainId=bob.id)
  GW-->>Dev: instance (validates chainId)

  Dev->>SC: getQuote(params)
  SC->>SDK: crossChainAPI GET /v0/transfer/quote<br/>fromChain, toChain, token addresses, symbols, amount
  SDK->>API: GET /v0/transfer/quote
  API-->>SDK: quote response (routes)
  SDK-->>SC: quote data

  alt routes > 0
    SC->>SDK: crossChainAPI POST /v0/transfer/send<br/>route, amounts, skipValidation=true
    SDK->>API: POST /v0/transfer/send
    API-->>SDK: send response
    SDK-->>SC: data
    SC-->>Dev: result
  else no routes / error
    note over SC: Throw error (non-ok, missing data, or zero routes)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

A swing of chains, a hop through rails,
I thump the logs, trace windy trails.
From BOB I bound to Base so keen,
Quotes in paw, my whiskers preen.
New routes to send, I twitch with glee—
Cross-chain carrots, gas-free spree! 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/swing

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new Swing client to handle cross-chain swaps. The implementation is a good start, but there are several critical issues that need to be addressed before this can be safely used. Specifically, user addresses are hardcoded to the zero address, which will cause transactions to fail or lead to loss of funds. The method for getting a quote also initiates a transfer, which is misleading. Additionally, the implementation has some risky shortcuts like skipping validation and always picking the first available route. The associated test file also lacks any assertions. I've left detailed comments on these points.

Comment on lines +6 to +14
interface SwingQuoteParams {
fromChain: string;
toChain: string;
fromTokenAddress: Hex;
toTokenAddress: Hex;
fromTokenSymbol: string;
toTokenSymbol: string;
tokenAmount: string;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This interface is missing fromUserAddress and toUserAddress. These are crucial for creating a valid transfer and should be included here to be passed to the getQuote method, instead of hardcoding zeroAddress.

interface SwingQuoteParams {
    fromChain: string;
    toChain: string;
    fromTokenAddress: Hex;
    toTokenAddress: Hex;
    fromTokenSymbol: string;
    toTokenSymbol: string;
    tokenAmount: string;
    fromUserAddress: Hex;
    toUserAddress: Hex;
}

Comment on lines +32 to +36
fromUserAddress: zeroAddress,
toChain: params.toChain,
toTokenSymbol: params.toTokenSymbol,
toTokenAddress: params.toTokenAddress,
toUserAddress: zeroAddress,
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

Hardcoding fromUserAddress and toUserAddress to zeroAddress is incorrect for real transactions. They should be passed in via params after updating the SwingQuoteParams interface.

                    fromUserAddress: params.fromUserAddress,
                    toChain: params.toChain,
                    toTokenSymbol: params.toTokenSymbol,
                    toTokenAddress: params.toTokenAddress,
                    toUserAddress: params.toUserAddress,

Comment on lines +56 to +62
fromUserAddress: zeroAddress,
tokenSymbol: params.fromTokenSymbol,
toTokenAddress: params.toTokenAddress,
toChain: params.toChain,
toTokenAmount: transferRoute.quote.amount,
toTokenSymbol: params.toTokenSymbol,
toUserAddress: zeroAddress,
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

Hardcoding fromUserAddress and toUserAddress to zeroAddress is incorrect for real transactions. They should be passed in via params.

                fromUserAddress: params.fromUserAddress,
                tokenSymbol: params.fromTokenSymbol,
                toTokenAddress: params.toTokenAddress,
                toChain: params.toChain,
                toTokenAmount: transferRoute.quote.amount,
                toTokenSymbol: params.toTokenSymbol,
                toUserAddress: params.toUserAddress,

});
}

async getQuote(params: SwingQuoteParams) {
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The method name getQuote is misleading because it doesn't just fetch a quote; it also makes a second API call to /v0/transfer/send to prepare a transfer. This can have unintended side effects and makes the code harder to understand. Consider renaming this method to something that better reflects its full functionality, like getTransferData, or splitting it into two separate methods: one to get a quote, and another to prepare the transfer using that quote.

throw new Error('No routes found');
}

const transferRoute = quoteResponse.data.routes[0];
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The implementation currently always selects the first route returned by the Swing API (quoteResponse.data.routes[0]). The API might return multiple routes with different fees, speeds, or final amounts. Hardcoding the first route might not be optimal for the user. Consider adding logic to select the best route based on some criteria (e.g., highest output amount) or allowing the caller to select a route.

tokenAmount: '1000000',
});

console.log('quote', quote);
Copy link
Contributor

Choose a reason for hiding this comment

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

high

This test case lacks assertions. A test should verify that the code behaves as expected by checking the output or state. Currently, it only calls the getQuote method and logs the result, but it doesn't validate if the returned quote object is correct. Please add assertions to check the properties of the quote object.

        console.log('quote', quote);

        assert.isDefined(quote, 'Quote should not be undefined');
        assert.isObject(quote, 'Quote should be an object');
        // TODO: Add more specific assertions based on the expected quote structure.

Comment on lines 47 to 55
"@magiceden-oss/runestone-lib": "^1.0.2",
"@scure/base": "^1.2.4",
"@scure/btc-signer": "^1.6.0",
"@swing.xyz/sdk": "^0.67.7",
"bitcoin-address-validation": "^3.0.0",
"bitcoinjs-lib": "^6.1.7",
"viem": "^2.33.2",
"global": "^4.4.0"
"global": "^4.4.0",
"viem": "^2.33.2"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better maintainability and to avoid merge conflicts, it's a good practice to keep dependencies sorted alphabetically. The devDependencies in this file are sorted, but dependencies are not. Please consider sorting the dependencies in this block.

Comment on lines +19 to +23
constructor() {
this.swingSDK = new SwingSDK({
projectId: 'bob-swap',
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The projectId is hardcoded. It would be more flexible to allow this to be passed in through the constructor, with 'bob-swap' as a default value. This would make the client more reusable.

    constructor(options: { projectId?: string } = {}) {
        this.swingSDK = new SwingSDK({
            projectId: options.projectId || 'bob-swap',
        });
    }

Comment on lines +42 to +44
if (!quoteResponse.response.ok) {
throw new Error(quoteResponse.response.statusText);
} else if (!quoteResponse.data) {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The error handling for a failed API response is minimal. Throwing just quoteResponse.response.statusText might not be enough for debugging. It would be helpful to include the status code and potentially the response body in the error message, as the API might provide more specific details about what went wrong.

        if (!quoteResponse.response.ok) {
            const errorBody = await quoteResponse.response.text().catch(() => 'Could not read error body.');
            throw new Error(`API Error: ${quoteResponse.response.status} ${quoteResponse.response.statusText}. Body: ${errorBody}`);
        }

Comment on lines +74 to +84
export class SwingGatewayClient extends GatewayApiClient {
private swingClient: SwingClient;

constructor(chainId: number, options?: { rpcUrl?: string }) {
if (chainId !== bob.id) {
throw new Error('SwingGatewayClient only supports BOB mainnet');
}
super(chainId, options);
this.swingClient = new SwingClient();
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The SwingGatewayClient class initializes a SwingClient instance, but it's never used. The class itself doesn't seem to add any new functionality. If this is a work-in-progress, it's fine, but as it stands, this is unused code. Consider adding methods that utilize swingClient or removing the class if it's not yet ready.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
sdk/test/swing.test.ts (1)

19-19: Remove console.log from tests

Replace with assertions; logs add noise in CI.

-        console.log('quote', quote);
+        assert.ok(quote, 'expected a quote/send response')
sdk/src/gateway/swing.ts (3)

19-23: Make projectId configurable and allow SDK injection for testing

Hardcoding 'bob-swap' hinders env-specific configs and mocking.

-    constructor() {
-        this.swingSDK = new SwingSDK({
-            projectId: 'bob-swap',
-        });
-    }
+    constructor(opts?: { projectId?: string; sdk?: SwingSDK }) {
+        this.swingSDK =
+            opts?.sdk ??
+            new SwingSDK({
+                projectId: opts?.projectId ?? process.env.SWING_PROJECT_ID ?? 'bob-swap',
+            });
+    }

42-48: Surface API error details for debuggability

Throwing only statusText loses server error context.

-        if (!quoteResponse.response.ok) {
-            throw new Error(quoteResponse.response.statusText);
+        if (!quoteResponse.response.ok) {
+            const body = await quoteResponse.response.text().catch(() => '');
+            throw new Error(`Quote failed: ${quoteResponse.response.status} ${quoteResponse.response.statusText} ${body}`);
         } else if (!quoteResponse.data) {
             throw new Error('No quote data');
         } else if (quoteResponse.data.routes.length === 0) {
             throw new Error('No routes found');
         }

73-84: Gateway wrapper is incomplete; expose quote/send and centralize chain gating

The class constructs SwingClient but doesn’t expose methods. Provide thin wrappers and reuse chain gating.

 export class SwingGatewayClient extends GatewayApiClient {
     private swingClient: SwingClient;

     constructor(chainId: number, options?: { rpcUrl?: string }) {
         if (chainId !== bob.id) {
             throw new Error('SwingGatewayClient only supports BOB mainnet');
         }
         super(chainId, options);
         this.swingClient = new SwingClient();
     }
+
+    quote(params: SwingQuoteParams) {
+        return this.swingClient.getQuote(params);
+    }
+
+    send(args: Parameters<SwingClient['send']>[0]) {
+        return this.swingClient.send(args);
+    }
 }

Also consider adding bob-sepolia support behind a feature flag.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 499f5b6 and af4ca94.

⛔ Files ignored due to path filters (1)
  • sdk/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • sdk/package.json (1 hunks)
  • sdk/src/gateway/swing.ts (1 hunks)
  • sdk/test/swing.test.ts (1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.27.2)
sdk/test/swing.test.ts

[high] 11-11: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 12-12: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 GitHub Check: Tests
sdk/src/gateway/swing.ts

[failure] 47-47: test/swing.test.ts > Swing Tests > should get quote
Error: No routes found
❯ SwingClient.getQuote src/gateway/swing.ts:47:19
❯ test/swing.test.ts:8:23

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (5)
sdk/package.json (2)

53-54: Dependency order churn is fine

Reordering "global" before "viem" is harmless. No action needed.


50-50: Verified Swing SDK dependency: @swing.xyz/sdk@^0.67.7 is the current latest, licensed under GPL-3.0-or-later, requires Node >= 16, and has no known vulnerabilities.

sdk/test/swing.test.ts (1)

11-12: Gitleaks false positive on EVM addresses

These 0x… token addresses tripped the “Generic API Key” rule. Consider allowlisting EVM address patterns in tests.

You can add to .gitleaks.toml:

[[allowlist.regexes]]
description = "Allow EVM addresses in tests"
regex = '''0x[a-fA-F0-9]{40}'''
paths = ["sdk/test/"]
sdk/src/gateway/swing.ts (2)

25-41: Ignore parameter naming suggestion
tokenSymbol is the correct key for the source token symbol (and toTokenSymbol for the destination) per the API spec—no changes required.

Likely an incorrect or invalid review comment.


65-66: skipValidation is defined as a string per the Swing API docs, so leaving 'true' is correct.

Likely an incorrect or invalid review comment.

Comment on lines +50 to +70
const transferRoute = quoteResponse.data.routes[0];

const sendResponse = await this.swingSDK.crossChainAPI.POST('/v0/transfer/send', {
body: {
fromChain: params.fromChain,
fromTokenAddress: params.fromTokenAddress,
fromUserAddress: zeroAddress,
tokenSymbol: params.fromTokenSymbol,
toTokenAddress: params.toTokenAddress,
toChain: params.toChain,
toTokenAmount: transferRoute.quote.amount,
toTokenSymbol: params.toTokenSymbol,
toUserAddress: zeroAddress,
tokenAmount: params.tokenAmount,
route: transferRoute.route,
skipValidation: 'true',
},
});

return sendResponse.data;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

getQuote() performs a send; wrong semantics, unsafe defaults (zeroAddress), and skipValidation as string

  • A method named getQuote must not initiate transfer side effects.
  • Sending to zeroAddress risks funds loss if this ever executes against mainnet.
  • skipValidation should be boolean unless the API requires string; verify.

Recommended split:

-    async getQuote(params: SwingQuoteParams) {
+    async getQuote(params: SwingQuoteParams) {
         const quoteResponse = await this.swingSDK.crossChainAPI.GET('/v0/transfer/quote', { ... });
         ...
-        const transferRoute = quoteResponse.data.routes[0];
-        const sendResponse = await this.swingSDK.crossChainAPI.POST('/v0/transfer/send', { body: { ... }});
-        return sendResponse.data;
+        return quoteResponse.data;
     }
+
+    async send(params: SwingQuoteParams & {
+        route: unknown;                  // use exact SDK type if available
+        fromUserAddress: Hex;
+        toUserAddress: Hex;
+        skipValidation?: boolean;
+    }) {
+        const sendResponse = await this.swingSDK.crossChainAPI.POST('/v0/transfer/send', {
+            body: {
+                fromChain: params.fromChain,
+                fromTokenAddress: params.fromTokenAddress,
+                fromUserAddress: params.fromUserAddress,
+                tokenSymbol: params.fromTokenSymbol,
+                toTokenAddress: params.toTokenAddress,
+                toChain: params.toChain,
+                toTokenAmount: (params as any).toTokenAmount ?? undefined,
+                toTokenSymbol: params.toTokenSymbol,
+                toUserAddress: params.toUserAddress,
+                tokenAmount: params.tokenAmount,
+                route: params.route,
+                skipValidation: params.skipValidation ?? false,
+            },
+        });
+        return sendResponse.data;
+    }

Also, select the best route (e.g., max toTokenAmount or min fee) instead of always routes[0].

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const transferRoute = quoteResponse.data.routes[0];
const sendResponse = await this.swingSDK.crossChainAPI.POST('/v0/transfer/send', {
body: {
fromChain: params.fromChain,
fromTokenAddress: params.fromTokenAddress,
fromUserAddress: zeroAddress,
tokenSymbol: params.fromTokenSymbol,
toTokenAddress: params.toTokenAddress,
toChain: params.toChain,
toTokenAmount: transferRoute.quote.amount,
toTokenSymbol: params.toTokenSymbol,
toUserAddress: zeroAddress,
tokenAmount: params.tokenAmount,
route: transferRoute.route,
skipValidation: 'true',
},
});
return sendResponse.data;
}
async getQuote(params: SwingQuoteParams) {
const quoteResponse = await this.swingSDK.crossChainAPI.GET(
'/v0/transfer/quote',
{ /* map params appropriately */ }
);
return quoteResponse.data;
}
async send(
params: SwingQuoteParams & {
route: unknown; // TODO: replace with exact SDK route type
fromUserAddress: Hex;
toUserAddress: Hex;
skipValidation?: boolean;
}
) {
const sendResponse = await this.swingSDK.crossChainAPI.POST(
'/v0/transfer/send',
{
body: {
fromChain: params.fromChain,
fromTokenAddress: params.fromTokenAddress,
fromUserAddress: params.fromUserAddress,
tokenSymbol: params.fromTokenSymbol,
toTokenAddress: params.toTokenAddress,
toChain: params.toChain,
toTokenAmount: (params as any).toTokenAmount ?? undefined,
toTokenSymbol: params.toTokenSymbol,
toUserAddress: params.toUserAddress,
tokenAmount: params.tokenAmount,
route: params.route,
skipValidation: params.skipValidation ?? false,
},
}
);
return sendResponse.data;
}

Comment on lines +5 to +19
it('should get quote', async () => {
const swingClient = new SwingClient();

const quote = await swingClient.getQuote({
fromChain: 'bob',
toChain: 'base',
fromTokenAddress: '0xe75D0fB2C24A55cA1e3F96781a2bCC7bdba058F0',
toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
fromTokenSymbol: 'USDC.e',
toTokenSymbol: 'USDC',
tokenAmount: '1000000',
});

console.log('quote', quote);
}, 120000);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Test makes live network calls, has no assertions, and fails CI

This is flaky and currently breaks CI (“No routes found”). Either mock Swing or gate as opt-in E2E. Also add assertions.

Apply one of the following:

Option A — mock with nock (preferred):

+import nock from 'nock';
 import { assert, describe, it } from 'vitest';
 import { SwingClient } from '../src/gateway/swing';

 describe('Swing Tests', () => {
-    it('should get quote', async () => {
+    it('should get quote', async () => {
+        nock.cleanAll();
+        const api = nock('https://api.swing.xyz')
+          .get('/v0/transfer/quote')
+          .query(true)
+          .reply(200, { routes: [{ quote: { amount: '990000' }, route: { id: 'r1' } }] });
+        nock('https://api.swing.xyz')
+          .post('/v0/transfer/send')
+          .reply(200, { id: 'tx_123', status: 'queued' });
         const swingClient = new SwingClient();
-        const quote = await swingClient.getQuote({
+        const quote = await swingClient.getQuote({
           ...
         });
-        console.log('quote', quote);
+        assert.ok(quote);
+        assert.equal(quote?.status, 'queued');
+        assert.ok(api.isDone());
     }, 120000);
 });

Option B — gate E2E and skip by default:

-describe('Swing Tests', () => {
-    it('should get quote', async () => {
+describe('Swing Tests', () => {
+    const runE2E = !!process.env.SWING_E2E;
+    (runE2E ? it : it.skip)('should get quote', async () => {
         const swingClient = new SwingClient();
         const quote = await swingClient.getQuote({ ... });
-        console.log('quote', quote);
+        assert.ok(quote);
     }, 120000);
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should get quote', async () => {
const swingClient = new SwingClient();
const quote = await swingClient.getQuote({
fromChain: 'bob',
toChain: 'base',
fromTokenAddress: '0xe75D0fB2C24A55cA1e3F96781a2bCC7bdba058F0',
toTokenAddress: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
fromTokenSymbol: 'USDC.e',
toTokenSymbol: 'USDC',
tokenAmount: '1000000',
});
console.log('quote', quote);
}, 120000);
import nock from 'nock';
import { assert, describe, it } from 'vitest';
import { SwingClient } from '../src/gateway/swing';
describe('Swing Tests', () => {
it('should get quote', async () => {
nock.cleanAll();
const api = nock('https://api.swing.xyz')
.get('/v0/transfer/quote')
.query(true)
.reply(200, { routes: [{ quote: { amount: '990000' }, route: { id: 'r1' } }] });
nock('https://api.swing.xyz')
.post('/v0/transfer/send')
.reply(200, { id: 'tx_123', status: 'queued' });
const swingClient = new SwingClient();
const quote = await swingClient.getQuote({
fromChain: 'bob',
toChain: 'base',
fromTokenAddress: '0x7C502F253124A88Bbb6a0Ad79D9BeD279d86E8f4',
toTokenAddress: '0x5C068df7139aD2Dedb840ceC95C384F25b443275',
fromTokenSymbol: 'USDC.e',
toTokenSymbol: 'USDC',
tokenAmount: '1000000',
});
assert.ok(quote);
assert.equal(quote?.status, 'queued');
assert.ok(api.isDone());
}, 120000);
});
🧰 Tools
🪛 Gitleaks (8.27.2)

[high] 11-11: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 12-12: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🤖 Prompt for AI Agents
In sdk/test/swing.test.ts around lines 5 to 19, the test makes live network
calls, has no assertions and is flaky in CI; replace the live call with a mocked
HTTP response (preferred) or gate the test as opt-in E2E and add assertions:
implement a nock interceptor for the Swing API endpoint(s) used by SwingClient
returning a representative JSON payload, call swingClient.getQuote in the test
against the mocked endpoint, and assert expected fields/values (e.g., routes
exist, expected token amounts/types); alternatively, wrap the test in a
conditional that only runs when a CI/E2E env var (e.g., RUN_E2E=true) is set and
add the same assertions so the test is skipped by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants