Skip to content

Add referral fee support (Legacy Swap) + live E2E scripts, docs, and config#3

Open
MonteCrypto999 wants to merge 17 commits intoelizaos-plugins:1.xfrom
MonteCrypto999:referral
Open

Add referral fee support (Legacy Swap) + live E2E scripts, docs, and config#3
MonteCrypto999 wants to merge 17 commits intoelizaos-plugins:1.xfrom
MonteCrypto999:referral

Conversation

@MonteCrypto999
Copy link

@MonteCrypto999 MonteCrypto999 commented Oct 25, 2025

PR: Optional Referral Fees on Jupiter Legacy Swap

This PR adds optional referral fees on Jupiter Legacy Swap via platformFeeBps and feeAccount,
with clean env-based config, tests, and runnable E2E examples (simulation/signing).


🧩 Features

Referral Fees

  • Adds platformFeeBps to /quote when REFERRAL_FEE_BPS > 0.

  • Derives feeAccount as the ATA for the chosen mint from REFERRAL_FEE_RECEIVER.

  • Modes:

    • sol_only: collect fees only if SOL/WSOL is in the pair.
    • smart: prefer SOL if available, else input mint.
  • Respects ExactIn / ExactOut rules and skips fees if constraints aren’t met.


🧠 Code Changes

  • src/service.ts

    • Integrates platformFeeBps in quote and feeAccount in swap.
    • Handles mint selection and fee receiver ATA derivation.
  • src/__tests__/integration.test.ts

    • Live-mode tests against Jupiter API (referral on/off, custom mint sample).
  • src/scripts/e2e-mainnet.ts

    • E2E script using .env (can simulate and sign/send).
  • local-e2e/ (gitignored)

    • Local simulator for build/quote/swap, simulate, and sign via Helius RPC.
  • Docs & Config

    • README.md and env.example: updated with clear config and E2E instructions.
    • .gitignore: ignores local-e2e and env files.

⚙️ How to Use

1. Setup Environment

Copy .env.example.env and set:

REFERRAL_FEE_BPS=20
REFERRAL_MODE=sol_only
REFERRAL_FEE_RECEIVER=<fee receiver public key>
TEST_INPUT_MINT=So11111111111111111111111111111111111111112
TEST_OUTPUT_MINT=Dz9mQ9NzkBcCsuGPFJ3r1bS4wgqKMHBPiVuniW8Mbonk
TEST_INPUT_AMOUNT_ATOMIC=5000000      # 0.005 SOL
TEST_SLIPPAGE_BPS=150
# Optional (for simulation/signing)
HELIUS_RPC_URL=<url>
SIGNER_SECRET_KEY=[…]
SEND_TX=0

2. Run E2E Example

npm run build && node dist/scripts/e2e-mainnet.js

3. Local Simulator (ignored, for ops/dev)

Populate local-e2e/.env.local as above, then run:

npm run local:sim

🧩 Notes & Edge Cases

  • Error “Invalid token account / 0x1789 (6025)”
    Usually a route/account issue.
    Ensure the fee receiver’s ATA exists for the selected mint (e.g., WSOL in sol_only mode).
    If absent, create the ATA or let Jupiter create it idempotently.
    Some routes may still fail in simulation—try higher slippage or another route.

  • No Referral Program needed for Legacy Swap.
    (Still required for Trigger API.)


🔗 References


✅ Checklist

  • Referral fee applied only when REFERRAL_FEE_BPS > 0 and valid feeAccount
  • feeAccount derived from REFERRAL_FEE_RECEIVER ATA for selected mint
  • Live integration tests against Jupiter API
  • E2E script with simulation/signing (HELIUS RPC)
  • README and .env.example updated
  • Local-only utilities ignored by git

Summary by CodeRabbit

  • New Features

    • Referral fee support with configurable fee modes and fee-account handling; JupiterService exposed at package root.
  • Documentation

    • Expanded README with features, installation, configuration, examples, and API usage.
    • Added environment example template for referral and e2e setup.
  • Tests

    • Added integration and referral tests plus an end-to-end mainnet test script.
  • Chores

    • Updated ignore patterns and package scripts/dependencies.
  • CI

    • Reworked release/publish workflows and release tooling configuration.

@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2025

Walkthrough

Adds referral-fee support and related tooling: environment-driven referral configuration, fee-mint selection and fee-account derivation, referral-aware quote and swap flows, tests, an E2E mainnet script, expanded docs, build entry, and CI/release workflow updates.

Changes

Cohort / File(s) Summary
Ignore & Env
\.gitignore, env.example
Added ignore patterns (doc/, .env*, local-e2e/); added env.example with referral vars, optional RPC/signing, and E2E test params.
Documentation
README.md
Renamed plugin to @elizaos/plugin-jupiter and substantially expanded documentation (features, installation, configuration, fee modes, examples, API, testing, backend endpoint guidance).
Package & Build
package.json, tsup.config.ts
Added devDependencies (dotenv, tsx) and scripts (e2e, local:sim); added src/scripts/e2e-mainnet.ts to tsup entry points.
Core Service & Types
src/service.ts, src/types.ts, src/index.ts
Implemented referral config loading, fee-mode and fee-mint selection, fee-account derivation (including WSOL handling), integrated platformFeeBps into quote/swap requests, added private route cache, exported JupiterService, and added new types FeeMode, ReferralConfig, FeeMintSelection.
E2E Script
src/scripts/e2e-mainnet.ts
New mainnet E2E script: env-driven config, quote → executeSwap → simulate, optional local signing and conditional send.
Tests
src/__tests__/integration.test.ts, src/__tests__/referral.test.ts
Added live integration tests covering referral fee handling, fee-account behavior, fee-mode selection, and env-var validation tests.
CI / Release
.github/workflows/npm-deploy.yml (removed), .github/workflows/publish-npm.yaml, .github/workflows/release.yml, release-please-config.json, .release-please-manifest.json
Removed old npm-deploy workflow; added new publish and auto-release workflows that delegate to reusable workflows; added release-please config and manifest files.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant E2EScript as E2E Script
    participant JupiterService
    participant Jupiter as Jupiter API
    participant Solana as Solana RPC

    User->>E2EScript: Run with env vars
    E2EScript->>E2EScript: Load config (mints, amount, keys)
    E2EScript->>JupiterService: Instantiate

    rect rgb(200,220,255)
        Note over E2EScript,JupiterService: Quote Phase
        E2EScript->>JupiterService: getQuote()
        JupiterService->>Jupiter: HTTP GET (includes platformFeeBps if enabled)
        Jupiter-->>JupiterService: Quote response
        JupiterService-->>E2EScript: Quote (outAmount, platformFee)
    end

    rect rgb(200,255,220)
        Note over E2EScript,JupiterService: Swap Phase
        E2EScript->>JupiterService: executeSwap(quote)
        JupiterService->>JupiterService: selectFeeMint() / deriveFeeAccount()
        JupiterService->>Jupiter: HTTP POST (swap + optional feeAccount)
        Jupiter-->>JupiterService: Swap transaction (base64)
        JupiterService-->>E2EScript: Swap transaction
    end

    rect rgb(255,240,200)
        Note over E2EScript,Solana: Simulation & Signing
        E2EScript->>Solana: simulateTransaction()
        Solana-->>E2EScript: Simulation result/logs
        alt SIGNER_SECRET_KEY provided
            E2EScript->>E2EScript: Sign locally
            alt SEND_TX enabled
                E2EScript->>Solana: sendTransaction()
                Solana-->>E2EScript: Tx signature
            else
                E2EScript->>E2EScript: Log signed tx only
            end
        else
            E2EScript->>E2EScript: Log unsigned tx warning
        end
    end
Loading
sequenceDiagram
    participant Client
    participant JupiterService
    participant Referral as Referral Logic

    Client->>JupiterService: getQuote()/executeSwap()
    JupiterService->>Referral: loadReferralConfig()
    Referral-->>JupiterService: {enabled, feeBps, mode}

    alt Referral enabled
        JupiterService->>JupiterService: include platformFeeBps in requests
        JupiterService->>Referral: selectFeeMint(pair, mode)
        Referral-->>JupiterService: {mint, feeAccount?}
        alt feeAccount needed
            JupiterService->>Referral: deriveFeeAccount(mint)
            Referral-->>JupiterService: feeAccount (ATA)
            JupiterService->>JupiterService: attach feeAccount to swap
        else
            JupiterService->>JupiterService: proceed without feeAccount
        end
    else
        JupiterService->>JupiterService: omit referral fields
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

  • Focus review on:
    • src/service.ts — referral config parsing, fee-mint selection logic (SOL/WSOL behavior), ATA derivation, integration into quote/swap requests, and added caching.
    • src/scripts/e2e-mainnet.ts — env handling, transaction deserialization/simulation, signing and send flow.
    • Tests — validity and flakiness risk of live integration tests.
    • CI/release files and release-please config — ensure delegation and triggers match repo policies.

Poem

🐰 Hopped in code with nimble paws,

Fees found homes without a pause,
Mints and accounts now neatly spun,
Tests and scripts say “well done, fun!”
Tiny rabbit cheers — hops, hop, run! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.67% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "Add referral fee support (Legacy Swap) + live E2E scripts, docs, and config" accurately and comprehensively summarizes the primary changes in this changeset. The title correctly identifies the main feature being added (referral fee support with the "(Legacy Swap)" clarification), along with the supporting additions (E2E scripts, documentation, and configuration files). The language is specific and descriptive, avoiding vague terms or noise, and provides sufficient context for a developer reviewing the repository history to understand the scope of changes. The title properly captures the breadth of this PR, which includes core feature work in src/service.ts and types.ts, test coverage, scripting, documentation expansion, and CI/CD workflow updates.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/service.ts (3)

68-94: Add HTTP timeouts to avoid hanging requests.

fetch has no timeout. Use AbortController and pass a per-try timeout; reuse in swap as well.

-async function getQuoteWithRetry(url: string, retries = 3, delay = 2000) {
+async function getQuoteWithRetry(url: string, retries = 3, delay = 2000, timeoutMs = 10_000) {
   for (let i = 0; i < retries; i++) {
-    const response = await fetch(url);
+    const c = new AbortController();
+    const to = setTimeout(() => c.abort(), timeoutMs);
+    const response = await fetch(url, { signal: c.signal }).finally(() => clearTimeout(to));

Replicate for getSwapWithRetry.


159-165: Avoid logging full swap payloads. Potential PII/log bloat.

payload.body can be large and contain user public keys. Log minimal fields or gate under a DEBUG flag.

-    console.log('jupSrv - swap', payload.body)
+    logger.info('jupSrv - swap request enqueued');

262-268: Don’t return false on invalid input; throw a typed error.

getQuote currently returns false, creating a union type with objects and risking downstream crashes. Throw a clear error.

-      if (isNaN(intAmount) || intAmount <= 0) {
-        console.warn('jupiter::getQuote - Amount in', amount, 'become', intAmount)
-        return false
-      }
+      if (!Number.isFinite(intAmount) || intAmount <= 0) {
+        throw new TypeError(`Invalid amount: ${amount}`);
+      }
🧹 Nitpick comments (19)
src/__tests__/referral.test.ts (1)

1-25: Consider enhancing or removing these basic tests.

These tests only verify that process.env can get and set values, which is standard JavaScript behavior. The comments (lines 3-5) acknowledge that actual referral logic testing happens in integration tests.

Consider either:

  1. Removing this file if integration tests already provide adequate coverage
  2. Expanding to test actual referral config parsing/validation logic (e.g., invalid BPS values, invalid modes, edge cases)
env.example (2)

4-5: Consider documenting the valid BPS range.

While 20 BPS (0.2%) is a reasonable example, it would be helpful to document the valid range. Based on the PR description mentioning "0-10%", the valid range appears to be 0-1000 BPS.

Consider adding a comment:

 # Platform fee in basis points. Example: 20 = 0.2%
+# Valid range: 0-1000 (0% - 10%)
 REFERRAL_FEE_BPS=20

19-19: Security reminder for secret key handling.

The example correctly shows the JSON array format for secret keys. However, consider adding a warning comment about never committing actual keys.

Consider adding a security note:

 # If you implement the recommended backend endpoint, you may need:
 # SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
+# WARNING: Never commit actual secret keys to version control
 # FEE_PAYER_SECRET_KEY=[1,2,3,...,64]

Also applies to: 29-29

README.md (2)

98-135: Verify error handling in the backend endpoint example.

The backend endpoint example is helpful, but consider these improvements:

  1. Validation: The endpoint checks for missing receiver and mint but doesn't validate that they are valid Solana public keys
  2. Error context: Generic error message e?.message ?? 'unknown error' could be more informative
  3. Security note: Missing warning about securing this endpoint (authentication, rate limiting)

Consider enhancing the example with:

 app.post('/api/fee-ata/ensure', async (req, res) => {
   try {
     const { receiver, mint } = req.body;
     if (!receiver || !mint) return res.status(400).json({ error: 'receiver and mint are required' });
+    
+    // Validate public keys
+    try {
+      new PublicKey(receiver);
+      new PublicKey(mint);
+    } catch {
+      return res.status(400).json({ error: 'Invalid public key format' });
+    }

     const connection = new Connection(RPC_URL, 'confirmed');

And add a security note before the endpoint code:

 ### Recommended backend endpoint (optional)

 For production setups, we recommend a backend endpoint to ensure the fee receiver has the required ATA before swaps:
+
+⚠️ **Security Note**: In production, add authentication and rate limiting to this endpoint.

173-200: Verify the E2E setup instructions are complete.

The testing section is comprehensive, but the E2E instructions assume the user will have the necessary infrastructure:

  1. The E2E script requires a funded signer wallet (SIGNER_SECRET_KEY) but doesn't mention funding requirements
  2. No mention of how to obtain/set up HELIUS_RPC_URL (free tier? paid?)
  3. The example uses 0.005 SOL - might be helpful to mention approximate USD cost

Consider adding a note before the E2E instructions:

 # Live E2E (example using .env.example), with optional simulation/signing
+# Note: E2E requires a funded wallet (~0.005-0.01 SOL) and Helius RPC access (free tier available)
 # 1) Copy .env.example to .env and set:
src/types.ts (1)

34-40: Consider adding JSDoc for the valid range of feeBps.

The ReferralConfig interface looks good, but the feeBps field would benefit from documentation about its valid range. Based on the PR description and README, the valid range is 0-1000 (representing 0% to 10%).

Consider adding JSDoc:

+/**
+ * Fee configuration for referral rewards
+ * @property enabled - Whether referral fees are enabled
+ * @property feeBps - Fee in basis points (valid range: 0-1000, representing 0%-10%)
+ * @property mode - Fee collection mode
+ */
 export interface ReferralConfig {
   enabled: boolean;
   feeBps: number;
   mode: FeeMode;
 }
src/__tests__/integration.test.ts (5)

21-27: Gate LIVE tests to avoid flaky CI runs.

These integration tests hit real Jupiter endpoints and mainnet. Gate the suite behind an env flag (e.g., RUN_LIVE_TESTS=1) to skip by default.

Apply:

-describe('Jupiter Service Integration (LIVE)', () => {
+const RUN_LIVE = process.env.RUN_LIVE_TESTS === '1';
+(RUN_LIVE ? describe : describe.skip)('Jupiter Service Integration (LIVE)', () => {

49-61: Simplify the “referral disabled” assertion.

Use a direct undefined/null check to avoid redundant comparison.

-      expect(quote.platformFee == null || quote.platformFee === null).toBe(true);
+      expect(quote.platformFee == null).toBe(true);

84-96: Ensure env cleanup between tests.

Some cases set REFERRAL_FEE_RECEIVER; others don’t. Add afterEach to restore a captured baseline env snapshot to avoid cross-test leakage.

+const ENV_SNAPSHOT = { ...process.env };
+afterEach(() => {
+  process.env = { ...ENV_SNAPSHOT };
+});

100-116: Strengthen fee-mode assertions.

For sol_only, also assert that no feeAccount is attempted when SOL isn’t in the pair (or that logs indicate fallback). Today the test only checks swapTransaction truthiness.

  • Consider mocking executeSwap’s HTTP call to assert feeAccount omission for USDC-USDT.

134-165: Live custom-mint test: guard and reduce blast radius.

This calls mainnet with an arbitrary mint. Keep under RUN_LIVE_TESTS, lower the amount further (e.g., 10_000) and optionally add retries/backoff locally to reduce flakiness.

src/scripts/e2e-mainnet.ts (2)

27-37: Surface referral config explicitly in logs.

Small UX: log whether referral is active and which mint mode is used to ease troubleshooting.

-  console.log('E2E Mainnet - Params', { inputMint, outputMint, amount, slippageBps, feeBps, mode, receiver });
+  const referralEnabled = feeBps > 0;
+  console.log('E2E Mainnet - Params', { inputMint, outputMint, amount, slippageBps, referralEnabled, feeBps, mode, receiver });

64-81: Harden secret parsing and guard log noise.

Validate SIGNER_SECRET_KEY format and avoid printing large structures or stack traces by default. Keep SEND_TX hard-gated.

-      const parsed = JSON.parse(signerSecret);
+      let parsed: number[];
+      try { parsed = JSON.parse(signerSecret) } catch { throw new Error('SIGNER_SECRET_KEY must be a JSON array of 64 bytes'); }
       const signer = Keypair.fromSecretKey(Uint8Array.from(parsed));
src/service.ts (6)

29-48: Fee mint selection: document exact behavior and ensure WSOL constant reuse.

Logic looks fine; add brief docs on ExactIn/ExactOut behavior if relevant, and reuse WSOL_MINT constant consistently across the file to avoid hard-coded duplicates.


53-66: Optionally verify ATA existence to fail fast.

Derivation succeeds even if the ATA doesn’t exist; swaps will later fail. Consider an optional preflight check against RPC to ensure the ATA exists when referral is enabled, and surface a clear warning/error.

Example (pseudo):

// if (checkExists) { const info = await connection.getAccountInfo(ata); if (!info) warn(...) }

96-142: Background queue starts at module load.

checkQuoteQueues and checkSwapQueues are launched globally, which creates side effects on import and perpetual timers. Move queue management into JupiterService.start()/stop() or guard with a singleton start flag.

-// start checking queues
-checkQuoteQueues()
+let queuesStarted = false;
+export function startQueues() {
+  if (!queuesStarted) { queuesStarted = true; checkQuoteQueues(); }
+}

Then call startQueues() from JupiterService.start().


295-324: Magic constants and duplicate WSOL literal.

Hard-coded 2,039,280 lamports and repeated WSOL literal can drift. Hoist WSOL_MINT usage and make rent estimate configurable or documented with a reference.

-    const isWrappedSol = initialQuote.inputMint === "So11111111111111111111111111111111111111112";
+    const isWrappedSol = initialQuote.inputMint === WSOL_MINT;

347-374: feeAccount derivation: handle missing REFERRAL_FEE_RECEIVER and ExactOut constraints.

You warn when receiver is missing, but still continue silently. Consider failing fast (or disabling fees) based on an env toggle. Also, if swap mode is ExactOut and the selected fee mint conflicts with constraints, skip adding feeAccount to honor constraints (documented in PR summary).

  • Add: read quoteResponse.swapMode and skip feeAccount if ExactOut and selected mint isn’t compatible.
  • Add: env flag REFERRAL_STRICT=1 to error when receiver is missing.

427-434: Propagate referral flag consistently through helper paths.

getTokenPrice (and other helpers) call getQuote with default slippage; ensure referral config is respected (it is via getQuote), and consider allowing an override to disable referral fees in these utility methods to avoid polluting analytics.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 8954b8e and 2a64939.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • .gitignore (1 hunks)
  • README.md (1 hunks)
  • env.example (1 hunks)
  • package.json (1 hunks)
  • src/__tests__/integration.test.ts (1 hunks)
  • src/__tests__/referral.test.ts (1 hunks)
  • src/index.ts (1 hunks)
  • src/scripts/e2e-mainnet.ts (1 hunks)
  • src/service.ts (17 hunks)
  • src/types.ts (1 hunks)
  • tsup.config.ts (1 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[style] ~137-~137: ‘prior to’ might be wordy. Consider a shorter alternative.
Context: ...')); Call it from your ops/scripts prior to swaps, e.g.: bash curl -X POST http...

(EN_WORDINESS_PREMIUM_PRIOR_TO)

🔇 Additional comments (11)
.gitignore (1)

1-7: LGTM!

The ignore patterns appropriately exclude build artifacts, dependencies, environment files, and local testing directories. The inclusion of various .env* patterns follows security best practices for preventing sensitive configuration from being committed.

package.json (2)

12-14: LGTM!

The devDependencies additions are appropriate for the new E2E and simulation capabilities. Both dotenv for environment configuration and tsx for executing TypeScript directly are standard choices.


21-22: LGTM!

The new scripts are well-structured:

  • e2e runs the compiled E2E mainnet script
  • local:sim uses tsx to execute TypeScript directly for faster local development
tsup.config.ts (1)

4-4: LGTM!

Adding the E2E mainnet script as a build entry point is appropriate and enables the npm run e2e script to execute the compiled output.

src/index.ts (1)

29-29: LGTM!

Publicly exporting JupiterService enables consumers to directly instantiate and use the service, which aligns with the enhanced API reference documentation in the README.

README.md (4)

1-14: LGTM!

The Features section provides a clear, concise overview of the plugin's capabilities, including the new referral fee functionality with well-defined fee modes.


37-62: LGTM!

The configuration documentation clearly explains the referral fee setup with practical examples. The fee mode comparison (with ✅/❌ indicators) makes the behavior immediately understandable.


148-171: LGTM!

The API Reference provides clear, practical examples with appropriate comments. The code snippets demonstrate both quote fetching and swap execution flows.


218-222: LGTM!

The References section provides relevant links to Jupiter and ElizaOS documentation, giving users clear paths to deeper information.

src/types.ts (1)

42-45: LGTM!

The FeeMintSelection interface appropriately models the fee mint and optional fee account (ATA might not exist yet). The optional feeAccount aligns with the documented behavior where swaps proceed without fees if the account doesn't exist.

src/__tests__/integration.test.ts (1)

28-47: The original review comment is incorrect—keep the existing assertion.

Jupiter's lite quote endpoint does return platformFee.feeBps in the response when platformFeeBps is passed in the query. The test assertion expect(quote.platformFee?.feeBps).toBe(20) is not brittle; it reliably tests the guaranteed API contract. The suggested refactor to check only presence or amount is unnecessary. No changes are needed.

Likely an incorrect or invalid review comment.

Comment on lines +64 to +83
describe('Swap with Fee Account', () => {
it('should include feeAccount when referral is enabled', async () => {
process.env.REFERRAL_FEE_BPS = '20';
process.env.REFERRAL_MODE = 'smart';
process.env.REFERRAL_FEE_RECEIVER = '11111111111111111111111111111111';

const quote: any = await service.getQuote({ inputMint: WSOL_MINT, outputMint: USDC_MINT, amount: 100000, slippageBps: 50 });

const swap = await service.executeSwap({
quoteResponse: quote,
userPublicKey: '11111111111111111111111111111111',
slippageBps: 50,
});
expect((swap as any).swapTransaction).toBeTruthy();

delete process.env.REFERRAL_FEE_BPS;
delete process.env.REFERRAL_MODE;
delete process.env.REFERRAL_FEE_RECEIVER;
});

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Test name vs assertion mismatch.

Name says “include feeAccount when referral is enabled,” but the test doesn’t assert feeAccount presence. Either assert that the request body carried feeAccount (via a stubbed/mocked swap request) or rename the test to reflect what’s actually verified.

  • Option A: stub swap endpoint to capture payload and assert feeAccount is set.
  • Option B: rename to “should draft a swap when referral is enabled.”
🤖 Prompt for AI Agents
In src/__tests__/integration.test.ts around lines 64 to 83, the test title
claims it verifies a feeAccount is included when referral is enabled but the
assertions never check for feeAccount; either (A) stub or mock the swap endpoint
to capture the outgoing request payload and add an assertion that payload
contains the expected feeAccount (REFERRAL_FEE_RECEIVER) when REFERRAL_* env
vars are set, or (B) if you don’t want to assert payloads, change the test name
to something accurate like "should draft a swap when referral is enabled" and
keep the existing assertions; implement one of these fixes and remove the unused
env var cleanup or leave it as-is.

Comment on lines +39 to +45
const userPublicKey = process.env.E2E_USER_PUBLIC_KEY || receiver || '11111111111111111111111111111111';

const swap = await service.executeSwap({
quoteResponse: quote as any,
userPublicKey,
slippageBps,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Require a realistic user public key or exit.

Defaulting to the system program (“111…”) for userPublicKey can yield unusable drafts. Fail early unless E2E_USER_PUBLIC_KEY is provided.

-  const userPublicKey = process.env.E2E_USER_PUBLIC_KEY || receiver || '11111111111111111111111111111111';
+  const userPublicKey = process.env.E2E_USER_PUBLIC_KEY || receiver;
+  if (!userPublicKey) {
+    console.error('E2E_USER_PUBLIC_KEY (or REFERRAL_FEE_RECEIVER as fallback) is required.');
+    process.exit(1);
+  }
📝 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 userPublicKey = process.env.E2E_USER_PUBLIC_KEY || receiver || '11111111111111111111111111111111';
const swap = await service.executeSwap({
quoteResponse: quote as any,
userPublicKey,
slippageBps,
});
const userPublicKey = process.env.E2E_USER_PUBLIC_KEY || receiver;
if (!userPublicKey) {
console.error('E2E_USER_PUBLIC_KEY (or REFERRAL_FEE_RECEIVER as fallback) is required.');
process.exit(1);
}
const swap = await service.executeSwap({
quoteResponse: quote as any,
userPublicKey,
slippageBps,
});
🤖 Prompt for AI Agents
In src/scripts/e2e-mainnet.ts around lines 39 to 45, the code currently falls
back to the system program key ('111...') when E2E_USER_PUBLIC_KEY is not set
which produces invalid drafts; change the logic to require a realistic user
public key by removing the '111...' fallback and the receiver fallback, and
instead check process.env.E2E_USER_PUBLIC_KEY at startup and if it's missing log
a clear error and exit (or throw) so the script fails early rather than
continuing with an invalid public key.

Comment on lines +15 to +24
function loadReferralConfig(): ReferralConfig {
const feeBps = parseInt(process.env.REFERRAL_FEE_BPS || '0', 10);
const mode = (process.env.REFERRAL_MODE || 'smart') as FeeMode;

return {
enabled: feeBps > 0,
feeBps,
mode,
};
}
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

🧩 Analysis chain

Validate and clamp REFERRAL_FEE_BPS; sanitize mode.

Parse failures or out-of-range BPS can silently enable/disable fees. Clamp to a sane range and whitelist modes.

-function loadReferralConfig(): ReferralConfig {
-  const feeBps = parseInt(process.env.REFERRAL_FEE_BPS || '0', 10);
-  const mode = (process.env.REFERRAL_MODE || 'smart') as FeeMode;
+function loadReferralConfig(): ReferralConfig {
+  const raw = process.env.REFERRAL_FEE_BPS;
+  const parsed = Number.isFinite(Number(raw)) ? Math.floor(Number(raw)) : 0;
+  // Clamp to [1..1000]; adjust if Jupiter publishes a stricter cap.
+  const feeBps = Math.max(0, Math.min(1000, parsed));
+  const modeStr = (process.env.REFERRAL_MODE || 'smart').toLowerCase();
+  const mode = (modeStr === 'sol_only' ? 'sol_only' : 'smart') as FeeMode;
   return {
     enabled: feeBps > 0,
     feeBps,
     mode,
   };
 }

🌐 Web query:

What are the documented limits (min/max) for `platformFeeBps` in Jupiter's swap/quote APIs?

💡 Result:

Short answer: Jupiter's public docs do not specify a numeric min/max for platformFeeBps. The docs show platformFeeBps as an optional integer (basis points) and give examples (e.g., 20 = 0.2%) but do not document a formal range — so the only documented constraint is that it's an integer BPS value and that passing it requires providing feeAccount in /swap. [1][2]

Sources:

  • Jupiter Swap API (quote) — shows platformFee/platformFeeBps in request/response but no min/max range. [1]
  • Jupiter docs "Add fees to swap" — example and notes on using platformFeeBps (no numeric bounds documented). [2]

Validate and clamp REFERRAL_FEE_BPS; sanitize mode.

Parse failures or out-of-range BPS can silently enable/disable fees. Although Jupiter's public docs don't specify a numeric limit for platformFeeBps, defensive clamping to a reasonable range and whitelisting modes prevents configuration errors.

-function loadReferralConfig(): ReferralConfig {
-  const feeBps = parseInt(process.env.REFERRAL_FEE_BPS || '0', 10);
-  const mode = (process.env.REFERRAL_MODE || 'smart') as FeeMode;
+function loadReferralConfig(): ReferralConfig {
+  const raw = process.env.REFERRAL_FEE_BPS;
+  const parsed = Number.isFinite(Number(raw)) ? Math.floor(Number(raw)) : 0;
+  // Clamp to [0..1000] as a reasonable defensive bound.
+  const feeBps = Math.max(0, Math.min(1000, parsed));
+  const modeStr = (process.env.REFERRAL_MODE || 'smart').toLowerCase();
+  const mode = (modeStr === 'sol_only' ? 'sol_only' : 'smart') as FeeMode;
   return {
     enabled: feeBps > 0,
     feeBps,
     mode,
   };
 }
🤖 Prompt for AI Agents
In src/service.ts around lines 15 to 24, the REFERRAL_FEE_BPS parsing and
REFERRAL_MODE casting are unsafe: parse failures or out-of-range values can
silently misconfigure fees and untrusted mode strings may be accepted. Change
parsing to treat non-numeric input as 0, clamp feeBps to a safe range (e.g.,
0–10000 for basis points), and compute enabled from the clamped value; for mode,
implement a whitelist of allowed strings (e.g., 'smart' and 'fixed') and default
to 'smart' if the env value is not in the whitelist. Ensure the returned
ReferralConfig uses the sanitized feeBps and mode.

Comment on lines +274 to 281
// Load referral config and add platformFeeBps if enabled
const referralConfig = loadReferralConfig();
let url = `https://lite-api.jup.ag/swap/v1/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${intAmount}&slippageBps=${slippageBps}`;

if (referralConfig.enabled && referralConfig.feeBps > 0) {
url += `&platformFeeBps=${referralConfig.feeBps}`;
logger.info(`Referral fees enabled: ${referralConfig.feeBps} bps (${referralConfig.feeBps / 100}%)`);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Big‑number safety for “amount”.

Parsing via number risks precision loss for large atomics. Accept string|number, preserve as string in URL, and avoid parseInt unless necessary.

-      const referralConfig = loadReferralConfig();
-      let url = `https://lite-api.jup.ag/swap/v1/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${intAmount}&slippageBps=${slippageBps}`;
+      const referralConfig = loadReferralConfig();
+      const amountStr = String(amount);
+      let url = `https://lite-api.jup.ag/swap/v1/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${encodeURIComponent(amountStr)}&slippageBps=${slippageBps}`;

Also update the method signature to allow amount: string | number.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/service.ts around lines 274 to 281, the code converts amount via parseInt
which can lose precision for large atomic values; update the method signature to
accept amount: string | number, remove parseInt usage, and build the URL using a
preserved string representation of the amount (e.g., const amountStr = typeof
amount === 'number' ? String(amount) : amount) so the exact atomic value is kept
when appended to the quote URL; keep referral platformFeeBps logic as-is and use
amountStr in place of intAmount in the URL.

Copy link

@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: 1

🧹 Nitpick comments (1)
.github/workflows/release.yml (1)

6-6: Clean up the branch condition comment.

Line 6 contains a non-English comment fragment ("ou master") that appears to be incomplete or a copy-paste artifact. Recommend removing or replacing with a clear English comment.

  on:
    push:
      branches:
-       - main  # ou master
+       - main
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 2a64939 and 0d500dc.

📒 Files selected for processing (5)
  • .github/workflows/npm-deploy.yml (0 hunks)
  • .github/workflows/publish-npm.yaml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .release-please-manifest.json (1 hunks)
  • release-please-config.json (1 hunks)
💤 Files with no reviewable changes (1)
  • .github/workflows/npm-deploy.yml
🔇 Additional comments (4)
.release-please-manifest.json (1)

1-3: LGTM!

Standard release-please manifest structure. The "0.0.0" initial version is appropriate for release-please automation.

release-please-config.json (1)

1-26: LGTM!

Well-structured release-please configuration. Changelog sections are comprehensive, emoji formatting improves readability, and hidden sections (style, test, build, ci) appropriately exclude internal changes from public release notes. The simple release type and bump strategy are appropriate for this repository structure.

.github/workflows/publish-npm.yaml (2)

13-22: Verify explicit job permissions for npm publishing.

Default GitHub Actions permissions are read-only and insufficient for npm publishing. To publish packages, you must grant write permissions in the workflow. This workflow calls an external reusable workflow (sendo-labs/reusable-workflows) which may handle permissions internally, but this should be verified. Either add explicit permissions to this job:

permissions:
  contents: read

Or verify the reusable workflow documentation confirms it handles the necessary write permissions for npm publishing.


12-12: Yes, please search for the sendo-labs/reusable-workflows repository and the npm-publish-workflow-v1.0.0 workflow on GitHub.

Verify external reusable workflow dependency and configuration.

The workflow delegates to sendo-labs/reusable-workflows@npm-publish-workflow-v1.0.0, but this repository could not be confirmed via public sources. Ensure:

  • The external repository exists and is from a trusted source
  • The version tag npm-publish-workflow-v1.0.0 is correct and stable
  • The reusable workflow properly handles the NPM_TOKEN secret (verify it's not logged or exposed)
  • Job permissions are appropriate for npm publishing (may need contents:read if the reusable workflow requires it)


jobs:
release:
uses: sendo-labs/reusable-workflows/.github/workflows/release.yml@release-workflow-v1.0.0
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

🧩 Analysis chain

Verify external reusable workflow dependency.

The workflow delegates to sendo-labs/reusable-workflows@release-workflow-v1.0.0. Ensure this external repository and version tag are correct and from a trusted source.


🌐 Web query:

sendo-labs reusable-workflows GitHub release-workflow-v1.0.0

💡 Result:

I couldn't find a public GitHub repository or a release/tag named "sendo-labs reusable-workflows release-workflow-v1.0.0". (I searched GitHub/docs for reusable-workflow usage but found no matching sendo-labs repo or that tag.) (github.com)

Do you want me to:

  • search GitHub again (I can try broader queries or GitHub's site search), or
  • check a specific URL/repo you have, or
  • show how to reference/use a reusable workflow tag like @v1.0.0 in a caller workflow?

Citations:


Reusable workflow reference cannot be verified—repository not found publicly.

The workflow references sendo-labs/reusable-workflows@release-workflow-v1.0.0, but no public GitHub repository with this name or tag exists. Verify:

  • The organization name is correct (sendo-labs)
  • The repository exists and you have access (if private)
  • The tag/release version is correct
  • The workflow path is correct (.github/workflows/release.yml)

If this is a private repository, document this external dependency clearly for maintainers.

🤖 Prompt for AI Agents
.github/workflows/release.yml around line 10: the workflow references
sendo-labs/reusable-workflows/.github/workflows/release.yml@release-workflow-v1.0.0
which cannot be found publicly; verify and correct the reusable workflow
reference by confirming the organization and repo name, ensuring the
.github/workflows/release.yml path exists in that repo, and that the tag
release-workflow-v1.0.0 is valid; if the repository is private, either replace
the reference with a local or public workflow copy in this repo or add
documentation in the repo README or CONTRIBUTING noting the private external
dependency and required access permissions for maintainers.

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.

3 participants

Comments