@lombard.finance/sdk-solana 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/index.cjs +1 -1
  2. package/dist/index.js +23 -22
  3. package/dist/index2.cjs +47 -47
  4. package/dist/index2.js +6103 -6029
  5. package/dist/sendBridgeTransaction.cjs +1 -1
  6. package/dist/sendBridgeTransaction.js +19 -19
  7. package/package.json +1 -1
  8. package/src/bridge/sendBridgeTransaction.ts +2 -2
  9. package/src/const/__tests__/rpcUrls.test.ts +73 -0
  10. package/src/const/errors.ts +0 -1
  11. package/src/const/getConfig.ts +30 -15
  12. package/src/const/rpcUrls.ts +53 -7
  13. package/src/idl/asset_router.json +182 -1532
  14. package/src/idl/bridge.json +139 -1185
  15. package/src/idl/consortium.json +62 -498
  16. package/src/idl/getAssetRouterIdl.ts +1 -3
  17. package/src/idl/getConsortiumIdl.ts +1 -3
  18. package/src/idl/getLbtcIdl.ts +4 -1
  19. package/src/idl/getMailboxIdl.ts +1 -3
  20. package/src/idl/lombard_token_pool.json +92 -932
  21. package/src/idl/mailbox.json +114 -1018
  22. package/src/idl/ratio_oracle.json +35 -332
  23. package/src/services/SolanaServiceImpl.test.ts +5 -4
  24. package/src/stories/components/ConnectButton/ConnectButton.tsx +2 -2
  25. package/src/stories/components/NetworkSelector/NetworkSelector.tsx +1 -1
  26. package/src/stories/components/OutputSelector/OutputSelector.tsx +4 -4
  27. package/src/stories/components/SelectField/SelectField.tsx +2 -3
  28. package/src/stories/hooks/useFetchOutputs.ts +1 -1
  29. package/src/stories/utils/fromCamelCase.ts +1 -1
  30. package/src/types/sdkTypes.ts +7 -0
  31. package/src/utils/createDebugLogger.ts +1 -1
  32. package/src/web3Sdk/claimToken/claimBtcb.ts +19 -5
  33. package/src/web3Sdk/claimToken/claimLbtcGmp.ts +55 -25
  34. package/src/web3Sdk/claimToken/claimToken.stories.tsx +4 -3
  35. package/src/web3Sdk/claimToken/claimToken.ts +16 -6
  36. package/src/web3Sdk/claimToken/shared.ts +18 -12
  37. package/src/web3Sdk/claimToken/utils/__tests__/signatureUtils.test.ts +10 -4
  38. package/src/web3Sdk/claimToken/utils/signatureUtils.ts +3 -1
  39. package/src/web3Sdk/deposit/deposit.stories.tsx +1 -4
  40. package/src/web3Sdk/deposit/deposit.test.ts +67 -37
  41. package/src/web3Sdk/deposit/deposit.ts +14 -14
  42. package/src/web3Sdk/detectWallet/detectWallet.test.ts +2 -2
  43. package/src/web3Sdk/getBalance/getBalance.test.ts +1 -1
  44. package/src/web3Sdk/getBalance/getBalance.ts +2 -1
  45. package/src/web3Sdk/getTokenFeeConfig/getTokenFeeConfig.stories.tsx +8 -18
  46. package/src/web3Sdk/getTokenFeeConfig/getTokenFeeConfig.ts +2 -4
  47. package/src/web3Sdk/redeem/redeem.stories.tsx +17 -8
  48. package/src/web3Sdk/redeem/redeem.test.ts +45 -13
  49. package/src/web3Sdk/redeem/redeem.ts +46 -20
  50. package/src/web3Sdk/redeemToken/redeemBtcb.ts +28 -12
  51. package/src/web3Sdk/redeemToken/redeemForBtc.stories.tsx +6 -6
  52. package/src/web3Sdk/redeemToken/redeemForBtc.test.ts +43 -13
  53. package/src/web3Sdk/redeemToken/redeemForBtc.ts +27 -14
  54. package/src/web3Sdk/redeemToken/redeemLbtc.ts +28 -12
  55. package/src/web3Sdk/redeemToken/shared.test.ts +6 -2
  56. package/src/web3Sdk/redeemToken/shared.ts +1 -3
@@ -1 +1 @@
1
- "use strict";var _=Object.defineProperty;var $=(e,t,s)=>t in e?_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var r=(e,t,s)=>$(e,typeof t!="symbol"?t+"":t,s);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("./index2.cjs"),j=require("bignumber.js"),v=require("./quoteBridgeFee.cjs");function E(e){return{message:n.fromWeb3JsMessage(e.message),serializedMessage:e.message.serialize(),signatures:e.signatures}}function B(e){return new n.VersionedTransaction(n.toWeb3JsMessage(e.message),e.signatures)}class A extends n.UmiError{constructor(s,i){super(s,"plugin","Wallet Adapters",i);r(this,"name","WalletAdaptersError")}}class V extends A{constructor(){super('The current wallet adapter is not initialized. You likely have selected a wallet adapter but forgot to initialize it. You may do this by running the following asynchronous method: "wallet.connect();".');r(this,"name","UninitializedWalletAdapterError")}}class g extends A{constructor(s){const i=`The current wallet adapter does not support the following operation: [${s}]. Ensure your wallet is connected using a compatible wallet adapter.`;super(i);r(this,"name","OperationNotSupportedByWalletAdapterError")}}const Y=e=>({get publicKey(){if(!e.publicKey)throw new V;return n.fromWeb3JsPublicKey(e.publicKey)},async signMessage(t){if(e.signMessage===void 0)throw new g("signMessage");return e.signMessage(t)},async signTransaction(t){if(e.signTransaction===void 0)throw new g("signTransaction");return E(await e.signTransaction(B(t)))},async signAllTransactions(t){if(e.signAllTransactions===void 0)throw new g("signAllTransactions");const s=t.map(B);return(await e.signAllTransactions(s)).map(E)}}),G=8,H=4e5;async function Q({provider:e,env:t,destinationLzEndpointId:s,amount:i,recipientAddress:d,minAmountLd:k,options:M=new Uint8Array}){if(!e.publicKey)throw new Error("Wallet provider not connected.");if(!e.signTransaction)throw new Error("Wallet provider does not support signing transactions.");if(!s)throw new Error("Destination chain LayerZero Endpoint ID is required.");const a=n.envToNetwork[t],m=n.getRpcEndpoint(a),W=n.getMinimalUmiInstance(m),c=n.getConnection(a,m),f=n.getConfig(t),y=n.getLBTCAddress(a),K=new n.PublicKey(y),S=n.publicKey(f.lzOftAdapter),L=n.publicKey(f.lzEscrow),P=n.publicKey(y),U=n.getAssociatedTokenAddressSync(K,e.publicKey,!1),F=n.publicKey(U.toBase58()),w=new j(i).shiftedBy(G);n.validateBridgeAmount(w);const l=BigInt(w.toFixed(0)),I=k??l*999n/1000n,p=await v.quoteBridgeFee({env:t,provider:e,sendParams:{dstEid:s,to:d,amountLD:Number(l),minAmountLD:1,extraOptions:"",composeMsg:"",oftCmd:""}}),z=p.nativeFee,C=p.lzTokenFee;try{const o=Y({publicKey:e.publicKey,signMessage:async R=>{const{signature:Z}=await e.signMessage(R);return new Uint8Array(Z)},signTransaction:e.signTransaction,signAllTransactions:e.signAllTransactions}),O=n.getRecipientBytes32(d),q=await n.oft302_exports.send(W.rpc,{payer:o,tokenMint:P,tokenEscrow:L,tokenSource:F},{to:O,dstEid:s,amountLd:l,minAmountLd:I,options:M,composeMsg:void 0,nativeFee:BigInt(z),lzTokenFee:BigInt(C)},{oft:S}),x=n.ComputeBudgetProgram.setComputeUnitLimit({units:H}),N=n.toWeb3JsInstruction(q.instruction),{blockhash:T,lastValidBlockHeight:D}=await c.getLatestBlockhash(),u=new n.Transaction().add(x).add(N);u.recentBlockhash=T,u.feePayer=e.publicKey;const J=await e.signTransaction(u),b=await c.sendRawTransaction(J.serialize(),{skipPreflight:!1}),h=await c.confirmTransaction({signature:b,blockhash:T,lastValidBlockHeight:D},"confirmed");if(h.value.err)throw new Error(`Solana transaction failed to confirm: ${JSON.stringify(h.value.err)}`);return b}catch(o){throw o instanceof Error&&X(o),new Error("An unknown error occurred while sending the LayerZero bridge transaction.")}}const X=e=>{throw console.error("Error sending Solana LayerZero transaction:",e),e.message.includes("slippage")||e.message.includes("MinimumAmount")?new Error("Bridge failed due to slippage or minimum amount not met. Please try again or adjust parameters."):e.message.includes("insufficient funds")?new Error("Insufficient SOL balance to cover transaction and bridge fees."):e.message.includes("Readonly Signer")||e.message.includes("identity")?new Error("Wallet connection issue: Readonly signer detected or identity not set. Ensure wallet is fully connected."):new Error(`Failed to send LayerZero bridge transaction: ${e.message}`)};exports.sendBridgeTransaction=Q;
1
+ "use strict";var _=Object.defineProperty;var $=(e,t,s)=>t in e?_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:s}):e[t]=s;var r=(e,t,s)=>$(e,typeof t!="symbol"?t+"":t,s);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const n=require("./index2.cjs"),j=require("bignumber.js"),V=require("./quoteBridgeFee.cjs");function E(e){return{message:n.fromWeb3JsMessage(e.message),serializedMessage:e.message.serialize(),signatures:e.signatures}}function B(e){return new n.VersionedTransaction(n.toWeb3JsMessage(e.message),e.signatures)}class A extends n.UmiError{constructor(s,i){super(s,"plugin","Wallet Adapters",i);r(this,"name","WalletAdaptersError")}}class Y extends A{constructor(){super('The current wallet adapter is not initialized. You likely have selected a wallet adapter but forgot to initialize it. You may do this by running the following asynchronous method: "wallet.connect();".');r(this,"name","UninitializedWalletAdapterError")}}class u extends A{constructor(s){const i=`The current wallet adapter does not support the following operation: [${s}]. Ensure your wallet is connected using a compatible wallet adapter.`;super(i);r(this,"name","OperationNotSupportedByWalletAdapterError")}}const G=e=>({get publicKey(){if(!e.publicKey)throw new Y;return n.fromWeb3JsPublicKey(e.publicKey)},async signMessage(t){if(e.signMessage===void 0)throw new u("signMessage");return e.signMessage(t)},async signTransaction(t){if(e.signTransaction===void 0)throw new u("signTransaction");return E(await e.signTransaction(B(t)))},async signAllTransactions(t){if(e.signAllTransactions===void 0)throw new u("signAllTransactions");const s=t.map(B);return(await e.signAllTransactions(s)).map(E)}}),H=8,v=4e5;async function Q({provider:e,env:t,destinationLzEndpointId:s,amount:i,recipientAddress:g,minAmountLd:k,options:M=new Uint8Array}){if(!e.publicKey)throw new Error("Wallet provider not connected.");if(!e.signTransaction)throw new Error("Wallet provider does not support signing transactions.");if(!s)throw new Error("Destination chain LayerZero Endpoint ID is required.");const d=n.envToNetwork[t],m=n.getRpcEndpoint(t),W=n.getMinimalUmiInstance(m),a=n.getConnection(d,m,t),f=n.getConfig(t),y=n.getLBTCAddress(d),K=new n.PublicKey(y),S=n.publicKey(f.lzOftAdapter),L=n.publicKey(f.lzEscrow),P=n.publicKey(y),U=n.getAssociatedTokenAddressSync(K,e.publicKey,!1),F=n.publicKey(U.toBase58()),w=new j(i).shiftedBy(H);n.validateBridgeAmount(w);const c=BigInt(w.toFixed(0)),I=k??c*999n/1000n,p=await V.quoteBridgeFee({env:t,provider:e,sendParams:{dstEid:s,to:g,amountLD:Number(c),minAmountLD:1,extraOptions:"",composeMsg:"",oftCmd:""}}),z=p.nativeFee,C=p.lzTokenFee;try{const o=G({publicKey:e.publicKey,signMessage:async R=>{const{signature:Z}=await e.signMessage(R);return new Uint8Array(Z)},signTransaction:e.signTransaction,signAllTransactions:e.signAllTransactions}),O=n.getRecipientBytes32(g),q=await n.oft302_exports.send(W.rpc,{payer:o,tokenMint:P,tokenEscrow:L,tokenSource:F},{to:O,dstEid:s,amountLd:c,minAmountLd:I,options:M,composeMsg:void 0,nativeFee:BigInt(z),lzTokenFee:BigInt(C)},{oft:S}),x=n.ComputeBudgetProgram.setComputeUnitLimit({units:v}),N=n.toWeb3JsInstruction(q.instruction),{blockhash:T,lastValidBlockHeight:D}=await a.getLatestBlockhash(),l=new n.Transaction().add(x).add(N);l.recentBlockhash=T,l.feePayer=e.publicKey;const J=await e.signTransaction(l),b=await a.sendRawTransaction(J.serialize(),{skipPreflight:!1}),h=await a.confirmTransaction({signature:b,blockhash:T,lastValidBlockHeight:D},"confirmed");if(h.value.err)throw new Error(`Solana transaction failed to confirm: ${JSON.stringify(h.value.err)}`);return b}catch(o){throw o instanceof Error&&X(o),new Error("An unknown error occurred while sending the LayerZero bridge transaction.")}}const X=e=>{throw console.error("Error sending Solana LayerZero transaction:",e),e.message.includes("slippage")||e.message.includes("MinimumAmount")?new Error("Bridge failed due to slippage or minimum amount not met. Please try again or adjust parameters."):e.message.includes("insufficient funds")?new Error("Insufficient SOL balance to cover transaction and bridge fees."):e.message.includes("Readonly Signer")||e.message.includes("identity")?new Error("Wallet connection issue: Readonly signer detected or identity not set. Ensure wallet is fully connected."):new Error(`Failed to send LayerZero bridge transaction: ${e.message}`)};exports.sendBridgeTransaction=Q;
@@ -1,12 +1,12 @@
1
1
  var q = Object.defineProperty;
2
2
  var V = (n, e, t) => e in n ? q(n, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[e] = t;
3
3
  var a = (n, e, t) => V(n, typeof e != "symbol" ? e + "" : e, t);
4
- import { V as $, t as j, f as v, U as Y, a as G, g as H, b as Q, c as X, d as nn, P as en, p as i, e as tn, v as sn, h as on, o as an, C as rn, i as cn, T as ln, j as un, k as gn } from "./index2.js";
4
+ import { V as $, t as j, f as Y, U as G, a as H, g as v, b as Q, c as X, d as nn, P as en, p as i, e as tn, v as sn, h as on, o as an, C as rn, i as cn, T as ln, j as un, k as gn } from "./index2.js";
5
5
  import dn from "bignumber.js";
6
6
  import { quoteBridgeFee as mn } from "./quoteBridgeFee.js";
7
7
  function E(n) {
8
8
  return {
9
- message: v(n.message),
9
+ message: Y(n.message),
10
10
  serializedMessage: n.message.serialize(),
11
11
  signatures: n.signatures
12
12
  };
@@ -14,7 +14,7 @@ function E(n) {
14
14
  function A(n) {
15
15
  return new $(j(n.message), n.signatures);
16
16
  }
17
- class B extends Y {
17
+ class B extends G {
18
18
  constructor(t, s) {
19
19
  super(t, "plugin", "Wallet Adapters", s);
20
20
  a(this, "name", "WalletAdaptersError");
@@ -26,7 +26,7 @@ class fn extends B {
26
26
  a(this, "name", "UninitializedWalletAdapterError");
27
27
  }
28
28
  }
29
- class g extends B {
29
+ class u extends B {
30
30
  constructor(t) {
31
31
  const s = `The current wallet adapter does not support the following operation: [${t}]. Ensure your wallet is connected using a compatible wallet adapter.`;
32
32
  super(s);
@@ -37,21 +37,21 @@ const pn = (n) => ({
37
37
  get publicKey() {
38
38
  if (!n.publicKey)
39
39
  throw new fn();
40
- return G(n.publicKey);
40
+ return H(n.publicKey);
41
41
  },
42
42
  async signMessage(e) {
43
43
  if (n.signMessage === void 0)
44
- throw new g("signMessage");
44
+ throw new u("signMessage");
45
45
  return n.signMessage(e);
46
46
  },
47
47
  async signTransaction(e) {
48
48
  if (n.signTransaction === void 0)
49
- throw new g("signTransaction");
49
+ throw new u("signTransaction");
50
50
  return E(await n.signTransaction(A(e)));
51
51
  },
52
52
  async signAllTransactions(e) {
53
53
  if (n.signAllTransactions === void 0)
54
- throw new g("signAllTransactions");
54
+ throw new u("signAllTransactions");
55
55
  const t = e.map(A);
56
56
  return (await n.signAllTransactions(t)).map(E);
57
57
  }
@@ -61,7 +61,7 @@ async function Bn({
61
61
  env: e,
62
62
  destinationLzEndpointId: t,
63
63
  amount: s,
64
- recipientAddress: d,
64
+ recipientAddress: g,
65
65
  minAmountLd: k,
66
66
  options: M = new Uint8Array()
67
67
  }) {
@@ -71,19 +71,19 @@ async function Bn({
71
71
  throw new Error("Wallet provider does not support signing transactions.");
72
72
  if (!t)
73
73
  throw new Error("Destination chain LayerZero Endpoint ID is required.");
74
- const r = un[e], m = H(r), W = Q(m), c = X(r, m), f = gn(e), p = nn(r), K = new en(p), U = i(f.lzOftAdapter), L = i(f.lzEscrow), P = i(p), S = tn(
74
+ const d = un[e], m = v(e), W = Q(m), r = X(d, m, e), f = gn(e), p = nn(d), K = new en(p), U = i(f.lzOftAdapter), L = i(f.lzEscrow), P = i(p), S = tn(
75
75
  K,
76
76
  n.publicKey,
77
77
  !1
78
78
  ), C = i(S.toBase58()), w = new dn(s).shiftedBy(wn);
79
79
  sn(w);
80
- const l = BigInt(w.toFixed(0)), I = k ?? l * 999n / 1000n, y = await mn({
80
+ const c = BigInt(w.toFixed(0)), I = k ?? c * 999n / 1000n, y = await mn({
81
81
  env: e,
82
82
  provider: n,
83
83
  sendParams: {
84
84
  dstEid: t,
85
- to: d,
86
- amountLD: Number(l),
85
+ to: g,
86
+ amountLD: Number(c),
87
87
  minAmountLD: 1,
88
88
  extraOptions: "",
89
89
  composeMsg: "",
@@ -99,7 +99,7 @@ async function Bn({
99
99
  },
100
100
  signTransaction: n.signTransaction,
101
101
  signAllTransactions: n.signAllTransactions
102
- }), x = on(d), N = await an.send(
102
+ }), x = on(g), N = await an.send(
103
103
  W.rpc,
104
104
  {
105
105
  payer: o,
@@ -110,7 +110,7 @@ async function Bn({
110
110
  {
111
111
  to: x,
112
112
  dstEid: t,
113
- amountLd: l,
113
+ amountLd: c,
114
114
  minAmountLd: I,
115
115
  options: M,
116
116
  composeMsg: void 0,
@@ -124,12 +124,12 @@ async function Bn({
124
124
  units: yn
125
125
  }), D = cn(
126
126
  N.instruction
127
- ), { blockhash: T, lastValidBlockHeight: J } = await c.getLatestBlockhash(), u = new ln().add(O).add(D);
128
- u.recentBlockhash = T, u.feePayer = n.publicKey;
129
- const R = await n.signTransaction(u), h = await c.sendRawTransaction(
127
+ ), { blockhash: T, lastValidBlockHeight: J } = await r.getLatestBlockhash(), l = new ln().add(O).add(D);
128
+ l.recentBlockhash = T, l.feePayer = n.publicKey;
129
+ const R = await n.signTransaction(l), h = await r.sendRawTransaction(
130
130
  R.serialize(),
131
131
  { skipPreflight: !1 }
132
- ), b = await c.confirmTransaction(
132
+ ), b = await r.confirmTransaction(
133
133
  {
134
134
  signature: h,
135
135
  blockhash: T,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lombard.finance/sdk-solana",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "Solana integration for the Lombard SDK - stake BTC and manage LBTC on Solana",
5
5
  "exports": {
6
6
  ".": {
@@ -77,9 +77,9 @@ export async function sendBridgeTransaction({
77
77
  }
78
78
 
79
79
  const network = envToNetwork[env];
80
- const rpcUrl = getRpcEndpoint(network);
80
+ const rpcUrl = getRpcEndpoint(env);
81
81
  const umi = getMinimalUmiInstance(rpcUrl);
82
- const connection = getConnection(network, rpcUrl);
82
+ const connection = getConnection(network, rpcUrl, env);
83
83
  const config = getConfig(env);
84
84
  const lbtcMintAddress = getLBTCAddress(network);
85
85
  const lbtcMintPublicKey = new Web3PublicKey(lbtcMintAddress);
@@ -0,0 +1,73 @@
1
+ import { Env } from '@lombard.finance/sdk-common';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { SolanaNetwork } from '../../types/network';
5
+ import { getRpcEndpoint } from '../getConfig';
6
+ import { getRpcUrl, getWsUrl } from '../rpcUrls';
7
+
8
+ const STAGE_HOST = 'bff.stage.lombard-fi.com';
9
+ const PROD_HOST = 'bff.prod.lombard-fi.com';
10
+
11
+ describe('Solana BFF env propagation', () => {
12
+ describe('getRpcUrl', () => {
13
+ it('uses stage host for non-prod env on mainnet', () => {
14
+ const url = getRpcUrl(SolanaNetwork.mainnet, Env.stage);
15
+ expect(url).toContain(STAGE_HOST);
16
+ expect(url).not.toContain(PROD_HOST);
17
+ expect(url).toContain('/solana');
18
+ });
19
+
20
+ it('uses prod host for prod env on mainnet', () => {
21
+ const url = getRpcUrl(SolanaNetwork.mainnet, Env.prod);
22
+ expect(url).toContain(PROD_HOST);
23
+ expect(url).toContain('/solana');
24
+ });
25
+
26
+ it('uses stage host for stage env on devnet', () => {
27
+ const url = getRpcUrl(SolanaNetwork.devnet, Env.stage);
28
+ expect(url).toContain(STAGE_HOST);
29
+ expect(url).toContain('/solana_devnet');
30
+ });
31
+ });
32
+
33
+ describe('getWsUrl', () => {
34
+ it('uses stage WS host for non-prod env', () => {
35
+ const url = getWsUrl(SolanaNetwork.mainnet, Env.stage);
36
+ expect(url.startsWith('wss://')).toBe(true);
37
+ expect(url).toContain(STAGE_HOST);
38
+ expect(url).toContain('chain=solana');
39
+ });
40
+
41
+ it('uses prod WS host for prod env', () => {
42
+ const url = getWsUrl(SolanaNetwork.mainnet, Env.prod);
43
+ expect(url).toContain(PROD_HOST);
44
+ expect(url).toContain('chain=solana');
45
+ });
46
+
47
+ it('builds correct devnet WS URL (regression: was using HTTP host)', () => {
48
+ const url = getWsUrl(SolanaNetwork.devnet, Env.stage);
49
+ expect(url.startsWith('wss://')).toBe(true);
50
+ expect(url).toContain('chain=solana_devnet');
51
+ });
52
+ });
53
+
54
+ describe('getRpcEndpoint(env)', () => {
55
+ it('routes stage env through stage BFF + devnet segment', () => {
56
+ const url = getRpcEndpoint(Env.stage);
57
+ expect(url).toContain(STAGE_HOST);
58
+ expect(url).not.toContain(PROD_HOST);
59
+ expect(url).toContain('/solana_devnet');
60
+ });
61
+
62
+ it('routes prod env through prod BFF + mainnet segment', () => {
63
+ const url = getRpcEndpoint(Env.prod);
64
+ expect(url).toContain(PROD_HOST);
65
+ expect(url).toContain('/solana');
66
+ });
67
+
68
+ it('routes dev/ibc env through stage BFF', () => {
69
+ expect(getRpcEndpoint(Env.dev)).toContain(STAGE_HOST);
70
+ expect(getRpcEndpoint(Env.ibc)).toContain(STAGE_HOST);
71
+ });
72
+ });
73
+ });
@@ -75,4 +75,3 @@ export const CLAIM_REJECTED_ERROR = createSdkError({
75
75
  code: ErrorCode.CLAIM_REJECTED,
76
76
  message: 'LBTC claim operation was rejected.',
77
77
  });
78
-
@@ -1,7 +1,7 @@
1
1
  import { Env } from '@lombard.finance/sdk-common';
2
2
 
3
3
  import { SolanaNetwork } from '../types';
4
- import { RPC_URLS } from './rpcUrls';
4
+ import { getRpcUrl, RPC_URLS } from './rpcUrls';
5
5
 
6
6
  /**
7
7
  * Default environment
@@ -157,9 +157,12 @@ const devnetConfig: IConfig = {
157
157
  ratioOracle: 'LomfreVHrrMrSpv54KCJ6AC1eKL8QbL1Ej28S3gwawa',
158
158
  bridge: 'Lom9Em2WzV7gvtttdub9LZSR8gLgtbzFDhFm1zMQRp6',
159
159
  lombardTokenPool: 'LomdWAg9hHyz3VrvK5wXTap7o348Ku2QJ2j2H8Etj3C',
160
- ledgerChainId: '031f51c4e4cc1dae1c752d2f8fe2ae045da668a13f2e47a465964d630f5ed22e',
161
- solanaRoutingChainId: '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab',
162
- bitcoinRoutingChainId: 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6',
160
+ ledgerChainId:
161
+ '031f51c4e4cc1dae1c752d2f8fe2ae045da668a13f2e47a465964d630f5ed22e',
162
+ solanaRoutingChainId:
163
+ '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab',
164
+ bitcoinRoutingChainId:
165
+ 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6',
163
166
  };
164
167
 
165
168
  const stageConfig: IConfig = {
@@ -209,9 +212,12 @@ const testnetConfig: IConfig = {
209
212
  ratioOracle: 'LomWze3gBt8Y7RN3sspuh2jupqAQPUi4tuaLWDnf6CZ',
210
213
  bridge: 'LombUtstgyrZUhjvi12hUnm7HG7CxhtanUv6hakuCm4',
211
214
  lombardTokenPool: 'Lomi5fKsXdGrrW2M3JRbJx5DnT3zgmSEfYWMUPeNaAB',
212
- ledgerChainId: '033bc7baf196ce32b8b9200518df11c35bad882fc6e3b6f45b4a8885f4c1281b',
213
- solanaRoutingChainId: '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab',
214
- bitcoinRoutingChainId: 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6',
215
+ ledgerChainId:
216
+ '033bc7baf196ce32b8b9200518df11c35bad882fc6e3b6f45b4a8885f4c1281b',
217
+ solanaRoutingChainId:
218
+ '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab',
219
+ bitcoinRoutingChainId:
220
+ 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6',
215
221
  };
216
222
 
217
223
  /**
@@ -235,9 +241,12 @@ const prodConfig: IConfig = {
235
241
  ratioOracle: 'LomyRmBeQiuqiwcPmr23RRA1SMLAuDEgj5tHHe7rsDn',
236
242
  bridge: 'Lomva5kTftuXE8992qRufkaeq3XrXV47qv3C1W9xW6Z',
237
243
  lombardTokenPool: 'Lomb8TTCwJKEhZrJ1J8UbsCRjNSf7NNQUEr4qo3dkSk',
238
- ledgerChainId: '0387b25e8e61f2ce4838b04795b231f09ee73ffd391da018bef4bc5c4975897b',
239
- solanaRoutingChainId: '02296998a6f8e2a784db5d9f95e18fc23f70441a1039446801089879b08c7ef0',
240
- bitcoinRoutingChainId: 'ff0000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
244
+ ledgerChainId:
245
+ '0387b25e8e61f2ce4838b04795b231f09ee73ffd391da018bef4bc5c4975897b',
246
+ solanaRoutingChainId:
247
+ '02296998a6f8e2a784db5d9f95e18fc23f70441a1039446801089879b08c7ef0',
248
+ bitcoinRoutingChainId:
249
+ 'ff0000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f',
241
250
  };
242
251
 
243
252
  /**
@@ -259,9 +268,11 @@ export function getConfig(env: Env = DEFAULT_ENV): IConfig {
259
268
  }
260
269
 
261
270
  /**
262
- * Get the RPC endpoint for a specific environment or network
263
- * @param envOrNetwork Environment or SolanaNetwork
264
- * @returns RPC endpoint
271
+ * Get the RPC endpoint for a specific environment or network.
272
+ *
273
+ * When called with an `Env`, both the BFF host (prod vs stage) and the
274
+ * Solana network segment are derived from the env, so a stage app never
275
+ * hits the prod BFF.
265
276
  */
266
277
  export function getRpcEndpoint(env: Env): string;
267
278
  export function getRpcEndpoint(network: SolanaNetwork): string;
@@ -273,8 +284,12 @@ export function getRpcEndpoint(envOrNetwork: Env | SolanaNetwork): string {
273
284
  envOrNetwork === 'dev' ||
274
285
  envOrNetwork === 'ibc';
275
286
 
276
- const network = isEnv ? envToNetwork[envOrNetwork] : envOrNetwork;
277
- return RPC_URLS[network];
287
+ if (isEnv) {
288
+ const env = envOrNetwork;
289
+ return getRpcUrl(envToNetwork[env], env);
290
+ }
291
+
292
+ return RPC_URLS[envOrNetwork];
278
293
  }
279
294
 
280
295
  /**
@@ -1,3 +1,4 @@
1
+ import { Env } from '@lombard.finance/sdk-common';
1
2
  import { Commitment, Connection } from '@solana/web3.js';
2
3
 
3
4
  import { SolanaNetwork } from '../types/network';
@@ -11,26 +12,71 @@ export const BFF_WS_URL_PROD = 'wss://bff.prod.lombard-fi.com/multi-rpc/proxy';
11
12
  export const BFF_WS_URL_STAGE =
12
13
  'wss://bff.stage.lombard-fi.com/multi-rpc/proxy';
13
14
 
15
+ const NETWORK_CHAIN_SEGMENT: Record<SolanaNetwork, string> = {
16
+ [SolanaNetwork.mainnet]: 'solana',
17
+ [SolanaNetwork.testnet]: 'solana_testnet',
18
+ [SolanaNetwork.devnet]: 'solana_devnet',
19
+ };
20
+
21
+ const isProdEnv = (env?: Env): boolean => env === 'prod';
22
+
23
+ const getBffBaseUrl = (env?: Env): string =>
24
+ isProdEnv(env) ? BFF_BASE_URL_PROD : BFF_BASE_URL_STAGE;
25
+
26
+ const getBffWsUrl = (env?: Env): string =>
27
+ isProdEnv(env) ? BFF_WS_URL_PROD : BFF_WS_URL_STAGE;
28
+
29
+ /**
30
+ * Default RPC URL map. Kept for backwards compatibility (Storybook, etc.).
31
+ * For env-aware URL resolution use {@link getRpcUrl} or {@link getConnection}.
32
+ */
14
33
  export const RPC_URLS: Record<SolanaNetwork, string> = {
15
34
  [SolanaNetwork.mainnet]: `${BFF_BASE_URL_PROD}/solana`,
16
35
  [SolanaNetwork.testnet]: 'https://api.testnet.solana.com',
17
- [SolanaNetwork.devnet]: `${BFF_BASE_URL_PROD}/solana_devnet`,
36
+ [SolanaNetwork.devnet]: `${BFF_BASE_URL_STAGE}/solana_devnet`,
18
37
  };
19
38
 
39
+ /**
40
+ * Default WebSocket URL map. Kept for backwards compatibility.
41
+ * For env-aware URL resolution use {@link getWsUrl} or {@link getConnection}.
42
+ */
20
43
  export const WS_URLS: Record<SolanaNetwork, string> = {
21
44
  [SolanaNetwork.mainnet]: `${BFF_WS_URL_PROD}?chain=solana`,
22
45
  [SolanaNetwork.testnet]: 'wss://api.testnet.solana.com',
23
- [SolanaNetwork.devnet]: `${BFF_BASE_URL_PROD}?chain=solana_devnet`,
46
+ [SolanaNetwork.devnet]: `${BFF_WS_URL_STAGE}?chain=solana_devnet`,
24
47
  };
25
48
 
26
- export const getRpcUrl = (network: SolanaNetwork) => {
27
- return RPC_URLS[network];
49
+ /**
50
+ * Returns the BFF-proxied RPC URL for a Solana network. When `env` is
51
+ * provided, the BFF host is selected accordingly (`prod` → bff.prod,
52
+ * any other env → bff.stage). Public endpoints (testnet) are returned as-is.
53
+ */
54
+ export const getRpcUrl = (network: SolanaNetwork, env?: Env): string => {
55
+ if (network === SolanaNetwork.testnet) {
56
+ return RPC_URLS[network];
57
+ }
58
+ return `${getBffBaseUrl(env)}/${NETWORK_CHAIN_SEGMENT[network]}`;
59
+ };
60
+
61
+ /**
62
+ * Returns the BFF-proxied WebSocket URL for a Solana network. Same env
63
+ * selection rules as {@link getRpcUrl}.
64
+ */
65
+ export const getWsUrl = (network: SolanaNetwork, env?: Env): string => {
66
+ if (network === SolanaNetwork.testnet) {
67
+ return WS_URLS[network];
68
+ }
69
+ return `${getBffWsUrl(env)}?chain=${NETWORK_CHAIN_SEGMENT[network]}`;
28
70
  };
29
71
 
30
- export const getConnection = (network: SolanaNetwork, rpcUrl?: string) => {
31
- return new Connection(rpcUrl || RPC_URLS[network], {
72
+ export const getConnection = (
73
+ network: SolanaNetwork,
74
+ rpcUrl?: string,
75
+ env?: Env,
76
+ ) => {
77
+ return new Connection(rpcUrl || getRpcUrl(network, env), {
32
78
  commitment: 'confirmed',
33
- wsEndpoint: WS_URLS[network],
79
+ wsEndpoint: getWsUrl(network, env),
34
80
  });
35
81
  };
36
82