@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
@@ -2,11 +2,7 @@ import { Env } from '@lombard.finance/sdk-common';
2
2
  import type { Meta, StoryObj } from '@storybook/react';
3
3
 
4
4
  import { envToNetwork, getConfig } from '../../const/getConfig';
5
- import {
6
- Button,
7
- CodeBlock,
8
- ErrorDisplay,
9
- } from '../../stories/components';
5
+ import { Button, CodeBlock, ErrorDisplay } from '../../stories/components';
10
6
  import { functionType } from '../../stories/decorators/function-type';
11
7
  import useQuery from '../../stories/hooks/useQuery';
12
8
  import {
@@ -24,14 +20,11 @@ interface StoryArgs {
24
20
  token: TokenChoice;
25
21
  }
26
22
 
27
- function resolveTokenMint(
28
- token: TokenChoice,
29
- env: Env,
30
- ): string | undefined {
23
+ function resolveTokenMint(token: TokenChoice, env: Env): string | undefined {
31
24
  const config = getConfig(env);
32
25
  return token === 'LBTC'
33
26
  ? config.lbtcTokenMint
34
- : config.btcbTokenMint ?? undefined;
27
+ : (config.btcbTokenMint ?? undefined);
35
28
  }
36
29
 
37
30
  export function StoryView({ environment, token }: StoryArgs) {
@@ -94,22 +87,19 @@ export function StoryView({ environment, token }: StoryArgs) {
94
87
  redeemForBtcMinAmount:
95
88
  configQuery.data.redeemForBtcMinAmount.toFormat(),
96
89
  maxMintCommission: configQuery.data.maxMintCommission.toFormat(),
97
- toNativeCommission:
98
- configQuery.data.toNativeCommission.toFormat(),
90
+ toNativeCommission: configQuery.data.toNativeCommission.toFormat(),
99
91
  'getRedeemFeeSolana (toNative + redeem)':
100
92
  redeemFeeQuery.data?.toFormat(),
101
- 'getMintingFeeSolana': mintingFeeQuery.data?.toFormat(),
102
- 'getMinRedeemAmountSolana': minRedeemQuery.data?.toFormat(),
103
- 'getMinRedeemAmountWithFeeSolana':
104
- minRedeemWithFeeQuery.data?.toFormat(),
93
+ getMintingFeeSolana: mintingFeeQuery.data?.toFormat(),
94
+ getMinRedeemAmountSolana: minRedeemQuery.data?.toFormat(),
95
+ getMinRedeemAmountWithFeeSolana: minRedeemWithFeeQuery.data?.toFormat(),
105
96
  }
106
97
  : undefined;
107
98
 
108
99
  return (
109
100
  <div>
110
101
  <p>
111
- <strong>Network:</strong> {network} | <strong>Token:</strong>{' '}
112
- {token}
102
+ <strong>Network:</strong> {network} | <strong>Token:</strong> {token}
113
103
  {tokenMint ? ` (${tokenMint})` : ' — not configured'}
114
104
  </p>
115
105
 
@@ -66,7 +66,7 @@ export async function getTokenFeeConfig(
66
66
  const mint = new PublicKey(mintAddress);
67
67
  const assetRouterProgramId = new PublicKey(config.assetRouter);
68
68
 
69
- const connection = getConnection(network, rpcUrl);
69
+ const connection = getConnection(network, rpcUrl, env);
70
70
 
71
71
  const [tokenConfigPDA] = PublicKey.findProgramAddressSync(
72
72
  [Buffer.from('token_config'), mint.toBuffer()],
@@ -77,9 +77,7 @@ export async function getTokenFeeConfig(
77
77
  const accountInfo = await connection.getAccountInfo(tokenConfigPDA);
78
78
 
79
79
  if (!accountInfo) {
80
- throw new Error(
81
- `TokenConfig account not found for mint ${mintAddress}`,
82
- );
80
+ throw new Error(`TokenConfig account not found for mint ${mintAddress}`);
83
81
  }
84
82
 
85
83
  if (accountInfo.data.length < TOKEN_CONFIG_MIN_SIZE) {
@@ -53,7 +53,8 @@ export const StoryView = ({
53
53
 
54
54
  const request = async () => {
55
55
  if (!provider || !address) throw new Error('Wallet not connected.');
56
- if (!recipient) throw new Error('Recipient address is required (set in args).');
56
+ if (!recipient)
57
+ throw new Error('Recipient address is required (set in args).');
57
58
  const parsedAmount = parseFloat(amount);
58
59
  if (!amount || isNaN(parsedAmount) || parsedAmount <= 0)
59
60
  throw new Error('Amount must be a positive number in BTC (set in args).');
@@ -89,7 +90,16 @@ export const StoryView = ({
89
90
  refetch: handleRedeem,
90
91
  } = useQuery(
91
92
  request,
92
- [provider, address, amount, recipient, tokenMint, toLchainId, toTokenAddress, environment],
93
+ [
94
+ provider,
95
+ address,
96
+ amount,
97
+ recipient,
98
+ tokenMint,
99
+ toLchainId,
100
+ toTokenAddress,
101
+ environment,
102
+ ],
93
103
  false,
94
104
  );
95
105
 
@@ -149,10 +159,7 @@ export const StoryView = ({
149
159
  />
150
160
  )}
151
161
  {(error || connectError) && (
152
- <ErrorDisplay
153
- error={error || connectError}
154
- title="Redeem Error"
155
- />
162
+ <ErrorDisplay error={error || connectError} title="Redeem Error" />
156
163
  )}
157
164
 
158
165
  {transactionLogs && transactionLogs.length > 0 && (
@@ -213,11 +220,13 @@ const meta: Meta<typeof StoryView> = {
213
220
  },
214
221
  toLchainId: {
215
222
  control: { type: 'text' },
216
- description: 'Destination Lombard routing chain ID (hex). Defaults to Solana routing chain ID',
223
+ description:
224
+ 'Destination Lombard routing chain ID (hex). Defaults to Solana routing chain ID',
217
225
  },
218
226
  toTokenAddress: {
219
227
  control: { type: 'text' },
220
- description: 'Destination token address/mint override (defaults to BTC.b from config)',
228
+ description:
229
+ 'Destination token address/mint override (defaults to BTC.b from config)',
221
230
  },
222
231
  },
223
232
  };
@@ -11,8 +11,10 @@ const MOCK_LBTC_MINT = 'LBTCojyVJ63rsEED2DLEGWMzSxWJyQynXE91LMLgV1J';
11
11
  const MOCK_BTCB_MINT = 'BTCB3ripBAut19jM8kDPVbJHb2ZdR2GcZvGZkCmFPtV8';
12
12
  const MOCK_ASSET_ROUTER = 'LomVyJDZ91jeVbNnTupJXKJTQFakJVMc87CmwDHYt95';
13
13
  const MOCK_MAILBOX = 'LomJw912MoUd7iiAesTQAgz1paLcTqi6ndG3w3pnKH9';
14
- const MOCK_SOLANA_CHAIN_ID = '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab';
15
- const MOCK_LEDGER_CHAIN_ID = '031f51c4e4cc1dae1c752d2f8fe2ae045da668a13f2e47a465964d630f5ed22e';
14
+ const MOCK_SOLANA_CHAIN_ID =
15
+ '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab';
16
+ const MOCK_LEDGER_CHAIN_ID =
17
+ '031f51c4e4cc1dae1c752d2f8fe2ae045da668a13f2e47a465964d630f5ed22e';
16
18
  const MOCK_PAYER = '8yarEiDaJVikHZbk3PQSoWiDn2T3oM1FHZN1Jv4VZFdr';
17
19
  const MOCK_RECIPIENT = 'DVMiNi7uxHEPABTBt1nLMoxnPniPKbLAFj4MPJq1RDjg';
18
20
  const MOCK_RECIPIENT_ATA = 'GRq2yasTvWWPPqSwxCZvqfCTfDhP3MswDH4nW2v6F5To';
@@ -25,7 +27,8 @@ const fullConfig: IConfig = {
25
27
  assetRouter: MOCK_ASSET_ROUTER,
26
28
  mailbox: MOCK_MAILBOX,
27
29
  solanaRoutingChainId: MOCK_SOLANA_CHAIN_ID,
28
- bitcoinRoutingChainId: 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6',
30
+ bitcoinRoutingChainId:
31
+ 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6',
29
32
  ledgerChainId: MOCK_LEDGER_CHAIN_ID,
30
33
  lbtcProgramId: 'HEY7PCJe3GB27UWdopuYb1xDbB5SNtTcYPxRjntvfBSA',
31
34
  treasuryAddress: 'ByHNGi4zPJw5StyWZoLQJ9n2wT12oupJF2pTSNKMnnAZ',
@@ -93,7 +96,11 @@ vi.mock('../../const/rpcUrls', () => ({
93
96
  }));
94
97
 
95
98
  vi.mock('../../utils/tokenAccount', () => ({
96
- getTokenProgramForMint: vi.fn().mockResolvedValue(new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')),
99
+ getTokenProgramForMint: vi
100
+ .fn()
101
+ .mockResolvedValue(
102
+ new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
103
+ ),
97
104
  }));
98
105
 
99
106
  vi.mock('../../idl/getAssetRouterIdl', () => ({
@@ -136,7 +143,9 @@ vi.mock('@solana/spl-token', () => ({
136
143
  return new PublicKey(MOCK_PAYER);
137
144
  },
138
145
  ),
139
- ASSOCIATED_TOKEN_PROGRAM_ID: new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'),
146
+ ASSOCIATED_TOKEN_PROGRAM_ID: new PublicKey(
147
+ 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
148
+ ),
140
149
  }));
141
150
 
142
151
  const mockTx = { instructions: [{ keys: [] }] };
@@ -157,7 +166,9 @@ vi.mock('../../utils', async () => {
157
166
  const actual = await vi.importActual('../../utils');
158
167
  return {
159
168
  ...actual,
160
- sendAndConfirmTransaction: vi.fn().mockResolvedValue({ signature: 'mock-redeem-sig' }),
169
+ sendAndConfirmTransaction: vi
170
+ .fn()
171
+ .mockResolvedValue({ signature: 'mock-redeem-sig' }),
161
172
  };
162
173
  });
163
174
 
@@ -194,7 +205,10 @@ describe('redeem', () => {
194
205
 
195
206
  it('should throw when Asset Router is not configured', async () => {
196
207
  const { getConfig } = await import('../../const/getConfig');
197
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, assetRouter: null });
208
+ vi.mocked(getConfig).mockReturnValueOnce({
209
+ ...fullConfig,
210
+ assetRouter: null,
211
+ });
198
212
 
199
213
  await expect(
200
214
  redeemFn({ publicKey: MOCK_PAYER } as any, baseParams),
@@ -212,7 +226,10 @@ describe('redeem', () => {
212
226
 
213
227
  it('should throw when Solana routing chain ID is not configured', async () => {
214
228
  const { getConfig } = await import('../../const/getConfig');
215
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, solanaRoutingChainId: null });
229
+ vi.mocked(getConfig).mockReturnValueOnce({
230
+ ...fullConfig,
231
+ solanaRoutingChainId: null,
232
+ });
216
233
 
217
234
  await expect(
218
235
  redeemFn({ publicKey: MOCK_PAYER } as any, baseParams),
@@ -221,7 +238,10 @@ describe('redeem', () => {
221
238
 
222
239
  it('should throw when source token mint is not resolved', async () => {
223
240
  const { getConfig } = await import('../../const/getConfig');
224
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, lbtcTokenMint: '' });
241
+ vi.mocked(getConfig).mockReturnValueOnce({
242
+ ...fullConfig,
243
+ lbtcTokenMint: '',
244
+ });
225
245
 
226
246
  await expect(
227
247
  redeemFn({ publicKey: MOCK_PAYER } as any, baseParams),
@@ -230,7 +250,10 @@ describe('redeem', () => {
230
250
 
231
251
  it('should throw when destination token is not configured', async () => {
232
252
  const { getConfig } = await import('../../const/getConfig');
233
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, btcbTokenMint: '' });
253
+ vi.mocked(getConfig).mockReturnValueOnce({
254
+ ...fullConfig,
255
+ btcbTokenMint: '',
256
+ });
234
257
 
235
258
  await expect(
236
259
  redeemFn({ publicKey: MOCK_PAYER } as any, baseParams),
@@ -239,7 +262,10 @@ describe('redeem', () => {
239
262
 
240
263
  it('should throw when amount is zero', async () => {
241
264
  await expect(
242
- redeemFn({ publicKey: MOCK_PAYER } as any, { ...baseParams, amount: '0' }),
265
+ redeemFn({ publicKey: MOCK_PAYER } as any, {
266
+ ...baseParams,
267
+ amount: '0',
268
+ }),
243
269
  ).rejects.toThrow('greater than zero');
244
270
  });
245
271
 
@@ -268,7 +294,10 @@ describe('redeem', () => {
268
294
 
269
295
  it('should throw when Ledger chain ID is not configured', async () => {
270
296
  const { getConfig } = await import('../../const/getConfig');
271
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, ledgerChainId: null });
297
+ vi.mocked(getConfig).mockReturnValueOnce({
298
+ ...fullConfig,
299
+ ledgerChainId: null,
300
+ });
272
301
 
273
302
  await expect(
274
303
  redeemFn({ publicKey: MOCK_PAYER } as any, baseParams),
@@ -301,7 +330,10 @@ describe('redeem', () => {
301
330
 
302
331
  it('should wrap errors with SolanaSdkError', async () => {
303
332
  const { getConfig } = await import('../../const/getConfig');
304
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, assetRouter: null });
333
+ vi.mocked(getConfig).mockReturnValueOnce({
334
+ ...fullConfig,
335
+ assetRouter: null,
336
+ });
305
337
 
306
338
  try {
307
339
  await redeemFn({ publicKey: MOCK_PAYER } as any, baseParams);
@@ -67,9 +67,13 @@ export async function redeem(
67
67
  params: RedeemParams,
68
68
  ): Promise<string> {
69
69
  const {
70
- amount, recipient, network,
71
- env: envOverride, rpcUrl,
72
- debug = false, skipPreflight = true,
70
+ amount,
71
+ recipient,
72
+ network,
73
+ env: envOverride,
74
+ rpcUrl,
75
+ debug = false,
76
+ skipPreflight = true,
73
77
  } = params;
74
78
  const { debugLog, printLogs } = createDebugLogger({ debug });
75
79
 
@@ -88,31 +92,40 @@ export async function redeem(
88
92
  throw new Error(`Mailbox not configured for network: ${network}`);
89
93
  }
90
94
  if (!config.solanaRoutingChainId) {
91
- throw new Error(`Solana routing chain ID not configured for network: ${network}`);
95
+ throw new Error(
96
+ `Solana routing chain ID not configured for network: ${network}`,
97
+ );
92
98
  }
93
99
 
94
100
  // Resolve source token mint (defaults to LBTC)
95
101
  const mintAddress = params.tokenMint || config.lbtcTokenMint;
96
102
  if (!mintAddress) {
97
- throw new Error(`Source token mint not configured for network: ${network}`);
103
+ throw new Error(
104
+ `Source token mint not configured for network: ${network}`,
105
+ );
98
106
  }
99
107
 
100
108
  // Resolve destination chain and token (defaults to Solana + BTC.b)
101
109
  const toLchainIdHex = params.toLchainId || config.solanaRoutingChainId;
102
110
  const toTokenAddressStr = params.toTokenAddress || config.btcbTokenMint;
103
111
  if (!toTokenAddressStr) {
104
- throw new Error(`Destination token not configured for network: ${network}`);
112
+ throw new Error(
113
+ `Destination token not configured for network: ${network}`,
114
+ );
105
115
  }
106
116
 
107
117
  validateAmount(amount);
108
118
 
109
- const connection = getConnection(network, rpcUrl);
119
+ const connection = getConnection(network, rpcUrl, env);
110
120
  const payer = new PublicKey(provider.publicKey);
111
121
  const mint = new PublicKey(mintAddress);
112
122
  const recipientPubkey = new PublicKey(recipient);
113
123
  const assetRouterProgramId = new PublicKey(config.assetRouter);
114
124
  const mailboxProgramId = new PublicKey(config.mailbox);
115
- const solanaRoutingChainId = Buffer.from(config.solanaRoutingChainId, 'hex');
125
+ const solanaRoutingChainId = Buffer.from(
126
+ config.solanaRoutingChainId,
127
+ 'hex',
128
+ );
116
129
  const toLchainId = Buffer.from(toLchainIdHex, 'hex');
117
130
  const toTokenAddress = new PublicKey(toTokenAddressStr).toBytes();
118
131
 
@@ -204,7 +217,10 @@ export async function redeem(
204
217
  throw new Error('Asset Router is paused');
205
218
  }
206
219
  debugLog('Asset Router treasury:', arTreasury.toBase58());
207
- debugLog('Native mint (recipient ATA mint):', nativeMintFromConfig.toBase58());
220
+ debugLog(
221
+ 'Native mint (recipient ATA mint):',
222
+ nativeMintFromConfig.toBase58(),
223
+ );
208
224
 
209
225
  if (mailboxConfigInfo.data.length < 104) {
210
226
  throw new Error(
@@ -246,10 +262,14 @@ export async function redeem(
246
262
 
247
263
  debugLog('Payer token account:', payerTokenAccount.toBase58());
248
264
  debugLog('Treasury token account:', treasuryTokenAccount.toBase58());
249
- debugLog('Recipient token account (payload):', recipientTokenAccount.toBase58());
265
+ debugLog(
266
+ 'Recipient token account (payload):',
267
+ recipientTokenAccount.toBase58(),
268
+ );
250
269
 
251
270
  // ── Balance check ──
252
- const tokenBalance = await connection.getTokenAccountBalance(payerTokenAccount);
271
+ const tokenBalance =
272
+ await connection.getTokenAccountBalance(payerTokenAccount);
253
273
  const userBalance = BigInt(tokenBalance.value.amount);
254
274
  const parsedAmount = BigInt(amount);
255
275
  if (userBalance < parsedAmount) {
@@ -258,10 +278,9 @@ export async function redeem(
258
278
  );
259
279
  }
260
280
 
261
- const assetRouterProgram = new Program(
262
- getAssetRouterIdl(env),
263
- { connection },
264
- );
281
+ const assetRouterProgram = new Program(getAssetRouterIdl(env), {
282
+ connection,
283
+ });
265
284
 
266
285
  // ── Instruction args ──
267
286
  const toLchainIdArray = Array.from(toLchainId);
@@ -271,7 +290,8 @@ export async function redeem(
271
290
  // ── Build & send with nonce retry ──
272
291
  const MAX_NONCE_RETRIES = 3;
273
292
  for (let attempt = 0; attempt < MAX_NONCE_RETRIES; attempt++) {
274
- const freshMailboxConfig = await connection.getAccountInfo(mailboxConfigPDA);
293
+ const freshMailboxConfig =
294
+ await connection.getAccountInfo(mailboxConfigPDA);
275
295
  if (!freshMailboxConfig) {
276
296
  throw new Error('Mailbox config account not found');
277
297
  }
@@ -289,10 +309,17 @@ export async function redeem(
289
309
  mailboxProgramId,
290
310
  );
291
311
 
292
- debugLog(`Attempt ${attempt + 1}: global nonce=${globalNonce}, outbound_message=${outboundMessagePDA.toBase58()}`);
312
+ debugLog(
313
+ `Attempt ${attempt + 1}: global nonce=${globalNonce}, outbound_message=${outboundMessagePDA.toBase58()}`,
314
+ );
293
315
 
294
316
  const tx = await assetRouterProgram.methods
295
- .redeem(toLchainIdArray, toTokenAddressArray, recipientArray, new BN(amount))
317
+ .redeem(
318
+ toLchainIdArray,
319
+ toTokenAddressArray,
320
+ recipientArray,
321
+ new BN(amount),
322
+ )
296
323
  .accounts({
297
324
  payer,
298
325
  config: assetRouterConfigPDA,
@@ -328,8 +355,7 @@ export async function redeem(
328
355
  return signature;
329
356
  } catch (err: unknown) {
330
357
  const isNonceError =
331
- err instanceof Error &&
332
- err.message.includes('0x7d6'); // ConstraintSeeds
358
+ err instanceof Error && err.message.includes('0x7d6'); // ConstraintSeeds
333
359
  if (isNonceError && attempt < MAX_NONCE_RETRIES - 1) {
334
360
  debugLog(`Nonce stale (ConstraintSeeds), retrying...`);
335
361
  continue;
@@ -16,12 +16,23 @@ import { BTC_NATIVE_TOKEN_ADDRESS, RedeemContext } from './shared';
16
16
  */
17
17
  export async function redeemBtcbForBtc(ctx: RedeemContext): Promise<string> {
18
18
  const {
19
- provider, params, config, connection,
20
- payer, mint, tokenProgramId, scriptPubKey,
21
- assetRouterProgramId, mailboxProgramId,
22
- solanaRoutingChainId, bitcoinRoutingChainId,
23
- assetRouterProgram, assetRouterConfigPDA, mailboxConfigPDA,
24
- arTreasury, mailboxTreasury,
19
+ provider,
20
+ params,
21
+ config,
22
+ connection,
23
+ payer,
24
+ mint,
25
+ tokenProgramId,
26
+ scriptPubKey,
27
+ assetRouterProgramId,
28
+ mailboxProgramId,
29
+ solanaRoutingChainId,
30
+ bitcoinRoutingChainId,
31
+ assetRouterProgram,
32
+ assetRouterConfigPDA,
33
+ mailboxConfigPDA,
34
+ arTreasury,
35
+ mailboxTreasury,
25
36
  debugLog,
26
37
  } = ctx;
27
38
 
@@ -53,7 +64,9 @@ export async function redeemBtcbForBtc(ctx: RedeemContext): Promise<string> {
53
64
 
54
65
  // ── Mailbox PDAs ──
55
66
  if (!config.ledgerChainId) {
56
- throw new Error(`Ledger chain ID not configured for network: ${params.network}`);
67
+ throw new Error(
68
+ `Ledger chain ID not configured for network: ${params.network}`,
69
+ );
57
70
  }
58
71
  const ledgerChainId = Buffer.from(config.ledgerChainId, 'hex');
59
72
  const [outboundMessagePathPDA] = PublicKey.findProgramAddressSync(
@@ -88,7 +101,8 @@ export async function redeemBtcbForBtc(ctx: RedeemContext): Promise<string> {
88
101
  debugLog('Treasury token account:', treasuryTokenAccount.toBase58());
89
102
 
90
103
  // ── Balance check ──
91
- const tokenBalance = await connection.getTokenAccountBalance(payerTokenAccount);
104
+ const tokenBalance =
105
+ await connection.getTokenAccountBalance(payerTokenAccount);
92
106
  const userBalance = BigInt(tokenBalance.value.amount);
93
107
  const parsedAmount = BigInt(amount);
94
108
  if (userBalance < parsedAmount) {
@@ -102,7 +116,8 @@ export async function redeemBtcbForBtc(ctx: RedeemContext): Promise<string> {
102
116
  // reads. Retry up to 3 times if the nonce becomes stale.
103
117
  const MAX_NONCE_RETRIES = 3;
104
118
  for (let attempt = 0; attempt < MAX_NONCE_RETRIES; attempt++) {
105
- const freshMailboxConfig = await connection.getAccountInfo(mailboxConfigPDA);
119
+ const freshMailboxConfig =
120
+ await connection.getAccountInfo(mailboxConfigPDA);
106
121
  if (!freshMailboxConfig) {
107
122
  throw new Error('Mailbox config account not found');
108
123
  }
@@ -121,7 +136,9 @@ export async function redeemBtcbForBtc(ctx: RedeemContext): Promise<string> {
121
136
  mailboxProgramId,
122
137
  );
123
138
 
124
- debugLog(`Attempt ${attempt + 1}: global nonce=${globalNonce}, outbound_message=${outboundMessagePDA.toBase58()}`);
139
+ debugLog(
140
+ `Attempt ${attempt + 1}: global nonce=${globalNonce}, outbound_message=${outboundMessagePDA.toBase58()}`,
141
+ );
125
142
 
126
143
  const tx = await assetRouterProgram.methods
127
144
  .redeemForBtc(scriptPubKey, new BN(amount))
@@ -160,8 +177,7 @@ export async function redeemBtcbForBtc(ctx: RedeemContext): Promise<string> {
160
177
  return signature;
161
178
  } catch (err: unknown) {
162
179
  const isNonceError =
163
- err instanceof Error &&
164
- err.message.includes('0x7d6'); // ConstraintSeeds
180
+ err instanceof Error && err.message.includes('0x7d6'); // ConstraintSeeds
165
181
  if (isNonceError && attempt < MAX_NONCE_RETRIES - 1) {
166
182
  debugLog(`Nonce stale (ConstraintSeeds), retrying...`);
167
183
  continue;
@@ -25,8 +25,11 @@ interface RedeemForBtcStoryArgs {
25
25
  token: RedeemToken;
26
26
  }
27
27
 
28
- const getTokenMint = (token: RedeemToken, config: ReturnType<typeof getConfig>): string | undefined =>
29
- token === 'LBTC' ? config.lbtcTokenMint : config.btcbTokenMint ?? undefined;
28
+ const getTokenMint = (
29
+ token: RedeemToken,
30
+ config: ReturnType<typeof getConfig>,
31
+ ): string | undefined =>
32
+ token === 'LBTC' ? config.lbtcTokenMint : (config.btcbTokenMint ?? undefined);
30
33
 
31
34
  export const StoryView = ({
32
35
  environment,
@@ -149,10 +152,7 @@ export const StoryView = ({
149
152
  />
150
153
  )}
151
154
  {(error || connectError) && (
152
- <ErrorDisplay
153
- error={error || connectError}
154
- title="Redeem Error"
155
- />
155
+ <ErrorDisplay error={error || connectError} title="Redeem Error" />
156
156
  )}
157
157
 
158
158
  {transactionLogs && transactionLogs.length > 0 && (
@@ -10,16 +10,23 @@ import { ErrorCode, SolanaSdkError } from '../../utils';
10
10
  const mockRedeemBtcbForBtc = vi.fn().mockResolvedValue('btcb-sig');
11
11
  const mockRedeemLbtcForBtc = vi.fn().mockResolvedValue('lbtc-sig');
12
12
 
13
- vi.mock('./redeemBtcb', () => ({ redeemBtcbForBtc: (...a: unknown[]) => mockRedeemBtcbForBtc(...a) }));
14
- vi.mock('./redeemLbtc', () => ({ redeemLbtcForBtc: (...a: unknown[]) => mockRedeemLbtcForBtc(...a) }));
13
+ vi.mock('./redeemBtcb', () => ({
14
+ redeemBtcbForBtc: (...a: unknown[]) => mockRedeemBtcbForBtc(...a),
15
+ }));
16
+ vi.mock('./redeemLbtc', () => ({
17
+ redeemLbtcForBtc: (...a: unknown[]) => mockRedeemLbtcForBtc(...a),
18
+ }));
15
19
 
16
20
  const MOCK_LBTC_MINT = 'LBTCojyVJ63rsEED2DLEGWMzSxWJyQynXE91LMLgV1J';
17
21
  const MOCK_BTCB_MINT = 'BTCB3ripBAut19jM8kDPVbJHb2ZdR2GcZvGZkCmFPtV8';
18
22
  const MOCK_ASSET_ROUTER = 'LomVyJDZ91jeVbNnTupJXKJTQFakJVMc87CmwDHYt95';
19
23
  const MOCK_MAILBOX = 'LomJw912MoUd7iiAesTQAgz1paLcTqi6ndG3w3pnKH9';
20
- const MOCK_SOLANA_CHAIN_ID = '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab';
21
- const MOCK_BITCOIN_CHAIN_ID = 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6';
22
- const MOCK_LEDGER_CHAIN_ID = '031f51c4e4cc1dae1c752d2f8fe2ae045da668a13f2e47a465964d630f5ed22e';
24
+ const MOCK_SOLANA_CHAIN_ID =
25
+ '0259db5080fc2c6d3bcf7ca90712d3c2e5e6c28f27f0dfbb9953bdb0894c03ab';
26
+ const MOCK_BITCOIN_CHAIN_ID =
27
+ 'ff000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6';
28
+ const MOCK_LEDGER_CHAIN_ID =
29
+ '031f51c4e4cc1dae1c752d2f8fe2ae045da668a13f2e47a465964d630f5ed22e';
23
30
  const MOCK_PAYER = '8yarEiDaJVikHZbk3PQSoWiDn2T3oM1FHZN1Jv4VZFdr';
24
31
 
25
32
  const fullConfig: IConfig = {
@@ -60,7 +67,11 @@ vi.mock('@lombard.finance/sdk-common', () => ({
60
67
  }));
61
68
 
62
69
  vi.mock('../../utils/tokenAccount', () => ({
63
- getTokenProgramForMint: vi.fn().mockResolvedValue(new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA')),
70
+ getTokenProgramForMint: vi
71
+ .fn()
72
+ .mockResolvedValue(
73
+ new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
74
+ ),
64
75
  }));
65
76
 
66
77
  vi.mock('../../idl/getAssetRouterIdl', () => ({
@@ -79,7 +90,9 @@ vi.mock('@coral-xyz/anchor', () => ({
79
90
  // Build AR config data: 8 (disc) + 32 (admin) + 32 (pending_admin) + 32 (treasury) + 1 (paused=0)
80
91
  function buildArConfigData(paused = false) {
81
92
  const data = Buffer.alloc(105, 0);
82
- const treasury = new PublicKey('8yarEiDaJVikHZbk3PQSoWiDn2T3oM1FHZN1Jv4VZFdr');
93
+ const treasury = new PublicKey(
94
+ '8yarEiDaJVikHZbk3PQSoWiDn2T3oM1FHZN1Jv4VZFdr',
95
+ );
83
96
  treasury.toBuffer().copy(data, 72);
84
97
  data[104] = paused ? 1 : 0;
85
98
  return data;
@@ -88,7 +101,9 @@ function buildArConfigData(paused = false) {
88
101
  // Build mailbox config data: 8 (disc) + 32 (admin) + 32 (pending_admin) + 32 (treasury) = 104
89
102
  function buildMailboxConfigData() {
90
103
  const data = Buffer.alloc(104, 0);
91
- const treasury = new PublicKey('8yarEiDaJVikHZbk3PQSoWiDn2T3oM1FHZN1Jv4VZFdr');
104
+ const treasury = new PublicKey(
105
+ '8yarEiDaJVikHZbk3PQSoWiDn2T3oM1FHZN1Jv4VZFdr',
106
+ );
92
107
  treasury.toBuffer().copy(data, 72);
93
108
  return data;
94
109
  }
@@ -154,7 +169,10 @@ describe('redeemForBtc', () => {
154
169
 
155
170
  it('should throw when Asset Router is not configured', async () => {
156
171
  const { getConfig } = await import('../../const/getConfig');
157
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, assetRouter: null });
172
+ vi.mocked(getConfig).mockReturnValueOnce({
173
+ ...fullConfig,
174
+ assetRouter: null,
175
+ });
158
176
 
159
177
  await expect(
160
178
  redeemForBtcFn(mockProvider as any, baseParams),
@@ -172,7 +190,10 @@ describe('redeemForBtc', () => {
172
190
 
173
191
  it('should throw when Solana routing chain ID is not configured', async () => {
174
192
  const { getConfig } = await import('../../const/getConfig');
175
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, solanaRoutingChainId: null });
193
+ vi.mocked(getConfig).mockReturnValueOnce({
194
+ ...fullConfig,
195
+ solanaRoutingChainId: null,
196
+ });
176
197
 
177
198
  await expect(
178
199
  redeemForBtcFn(mockProvider as any, baseParams),
@@ -181,7 +202,10 @@ describe('redeemForBtc', () => {
181
202
 
182
203
  it('should throw when Bitcoin routing chain ID is not configured', async () => {
183
204
  const { getConfig } = await import('../../const/getConfig');
184
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, bitcoinRoutingChainId: null });
205
+ vi.mocked(getConfig).mockReturnValueOnce({
206
+ ...fullConfig,
207
+ bitcoinRoutingChainId: null,
208
+ });
185
209
 
186
210
  await expect(
187
211
  redeemForBtcFn(mockProvider as any, baseParams),
@@ -190,7 +214,10 @@ describe('redeemForBtc', () => {
190
214
 
191
215
  it('should throw when tokenMint is BTC.b but env has no BTC.b mint configured', async () => {
192
216
  const { getConfig } = await import('../../const/getConfig');
193
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, btcbTokenMint: null });
217
+ vi.mocked(getConfig).mockReturnValueOnce({
218
+ ...fullConfig,
219
+ btcbTokenMint: null,
220
+ });
194
221
 
195
222
  await expect(
196
223
  redeemForBtcFn(mockProvider as any, baseParams),
@@ -282,7 +309,10 @@ describe('redeemForBtc', () => {
282
309
 
283
310
  it('should wrap errors with SolanaSdkError', async () => {
284
311
  const { getConfig } = await import('../../const/getConfig');
285
- vi.mocked(getConfig).mockReturnValueOnce({ ...fullConfig, assetRouter: null });
312
+ vi.mocked(getConfig).mockReturnValueOnce({
313
+ ...fullConfig,
314
+ assetRouter: null,
315
+ });
286
316
 
287
317
  try {
288
318
  await redeemForBtcFn(mockProvider as any, baseParams);