@lombard.finance/sdk-solana 1.2.2 → 2.0.0
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.
- package/README.md +48 -15
- package/dist/index.cjs +1 -1
- package/dist/index.js +37 -31
- package/dist/index2.cjs +57 -54
- package/dist/index2.js +7608 -7206
- package/package.json +2 -2
- package/src/const/errors.ts +0 -4
- package/src/const/getConfig.ts +43 -20
- package/src/const/rpcUrls.ts +2 -2
- package/src/idl/asset_router.json +548 -179
- package/src/idl/consortium.json +24 -43
- package/src/idl/mailbox.json +118 -107
- package/src/index.ts +1 -3
- package/src/services/SolanaServiceImpl.test.ts +123 -0
- package/src/services/SolanaServiceImpl.ts +53 -17
- package/src/stories/components/OutputSelector/OutputSelector.tsx +1 -0
- package/src/types/errors.ts +2 -0
- package/src/utils/createDebugLogger.ts +6 -13
- package/src/utils/errors.ts +2 -0
- package/src/utils/tokenAccount.ts +3 -1
- package/src/utils/transactions.ts +1 -1
- package/src/web3Sdk/claimToken/claimBtcb.ts +37 -28
- package/src/web3Sdk/claimToken/claimLbtcGmp.ts +66 -8
- package/src/web3Sdk/claimToken/claimToken.stories.tsx +2 -2
- package/src/web3Sdk/claimToken/claimToken.ts +20 -16
- package/src/web3Sdk/claimToken/constants.ts +5 -0
- package/src/web3Sdk/claimToken/index.ts +1 -0
- package/src/web3Sdk/claimToken/shared.ts +88 -80
- package/src/web3Sdk/deposit/deposit.stories.tsx +240 -0
- package/src/web3Sdk/deposit/deposit.test.ts +327 -0
- package/src/web3Sdk/deposit/deposit.ts +339 -0
- package/src/web3Sdk/deposit/index.ts +1 -0
- package/src/web3Sdk/getTokenFeeConfig/getTokenFeeConfig.stories.tsx +166 -0
- package/src/web3Sdk/getTokenFeeConfig/getTokenFeeConfig.test.ts +224 -0
- package/src/web3Sdk/getTokenFeeConfig/getTokenFeeConfig.ts +154 -0
- package/src/web3Sdk/getTokenFeeConfig/index.ts +11 -0
- package/src/web3Sdk/index.ts +3 -4
- package/src/web3Sdk/redeem/index.ts +1 -0
- package/src/web3Sdk/redeem/redeem.stories.tsx +226 -0
- package/src/web3Sdk/redeem/redeem.test.ts +327 -0
- package/src/web3Sdk/redeem/redeem.ts +352 -0
- package/src/web3Sdk/redeemToken/redeemBtcb.ts +174 -0
- package/src/web3Sdk/redeemToken/redeemForBtc.stories.tsx +35 -21
- package/src/web3Sdk/redeemToken/redeemForBtc.test.ts +306 -0
- package/src/web3Sdk/redeemToken/redeemForBtc.ts +54 -215
- package/src/web3Sdk/redeemToken/redeemLbtc.ts +174 -0
- package/src/web3Sdk/redeemToken/shared.test.ts +45 -0
- package/src/web3Sdk/redeemToken/shared.ts +97 -0
- package/src/web3Sdk/claimLBTC/claimLBTC.stories.tsx +0 -189
- package/src/web3Sdk/claimLBTC/claimLBTC.ts +0 -225
- package/src/web3Sdk/claimLBTC/index.ts +0 -1
- package/src/web3Sdk/claimLBTC/utils/generateDepositId.ts +0 -75
- package/src/web3Sdk/claimLBTC/utils/index.ts +0 -2
- package/src/web3Sdk/claimLBTC/utils/parseTransactionLogs.ts +0 -44
- package/src/web3Sdk/claimLBTC/utils/payloadUtils.ts +0 -58
- package/src/web3Sdk/claimLBTC/utils/postMintSignatures.ts +0 -50
- package/src/web3Sdk/unstakeLBTC/index.ts +0 -1
- package/src/web3Sdk/unstakeLBTC/unstakeLBTC.stories.tsx +0 -141
- package/src/web3Sdk/unstakeLBTC/unstakeLBTC.ts +0 -140
- /package/src/web3Sdk/{claimLBTC → claimToken}/utils/__tests__/signatureUtils.test.ts +0 -0
- /package/src/web3Sdk/{claimLBTC → claimToken}/utils/signatureUtils.ts +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { BN } from '@coral-xyz/anchor';
|
|
2
|
+
import {
|
|
3
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
4
|
+
getAssociatedTokenAddress,
|
|
5
|
+
} from '@solana/spl-token';
|
|
6
|
+
import { PublicKey, SystemProgram } from '@solana/web3.js';
|
|
7
|
+
|
|
8
|
+
import { sendAndConfirmTransaction } from '../../utils';
|
|
9
|
+
import { BTC_NATIVE_TOKEN_ADDRESS, RedeemContext } from './shared';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* LBTC → BTC redemption via Asset Router's `redeem_for_btc`.
|
|
13
|
+
*
|
|
14
|
+
* Burns LBTC tokens and sends a GMP message through the Mailbox to trigger
|
|
15
|
+
* a BTC payout to the specified Bitcoin address.
|
|
16
|
+
*/
|
|
17
|
+
export async function redeemLbtcForBtc(ctx: RedeemContext): Promise<string> {
|
|
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,
|
|
25
|
+
debugLog,
|
|
26
|
+
} = ctx;
|
|
27
|
+
|
|
28
|
+
const { amount, skipPreflight = false } = params;
|
|
29
|
+
|
|
30
|
+
// ── LBTC-specific PDAs ──
|
|
31
|
+
const [tokenConfigPDA] = PublicKey.findProgramAddressSync(
|
|
32
|
+
[Buffer.from('token_config'), mint.toBuffer()],
|
|
33
|
+
assetRouterProgramId,
|
|
34
|
+
);
|
|
35
|
+
const [tokenRoutePDA] = PublicKey.findProgramAddressSync(
|
|
36
|
+
[
|
|
37
|
+
Buffer.from('token_route'),
|
|
38
|
+
solanaRoutingChainId,
|
|
39
|
+
mint.toBuffer(),
|
|
40
|
+
bitcoinRoutingChainId,
|
|
41
|
+
BTC_NATIVE_TOKEN_ADDRESS,
|
|
42
|
+
],
|
|
43
|
+
assetRouterProgramId,
|
|
44
|
+
);
|
|
45
|
+
const [messagingAuthorityPDA] = PublicKey.findProgramAddressSync(
|
|
46
|
+
[Buffer.from('messaging_authority')],
|
|
47
|
+
assetRouterProgramId,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
debugLog('Token config PDA:', tokenConfigPDA.toBase58());
|
|
51
|
+
debugLog('Token route PDA:', tokenRoutePDA.toBase58());
|
|
52
|
+
debugLog('Messaging authority PDA:', messagingAuthorityPDA.toBase58());
|
|
53
|
+
|
|
54
|
+
// ── Mailbox PDAs ──
|
|
55
|
+
if (!config.ledgerChainId) {
|
|
56
|
+
throw new Error(`Ledger chain ID not configured for network: ${params.network}`);
|
|
57
|
+
}
|
|
58
|
+
const ledgerChainId = Buffer.from(config.ledgerChainId, 'hex');
|
|
59
|
+
const [outboundMessagePathPDA] = PublicKey.findProgramAddressSync(
|
|
60
|
+
[Buffer.from('outbound_message_path'), ledgerChainId],
|
|
61
|
+
mailboxProgramId,
|
|
62
|
+
);
|
|
63
|
+
const [senderConfigPDA] = PublicKey.findProgramAddressSync(
|
|
64
|
+
[Buffer.from('sender_config'), assetRouterProgramId.toBuffer()],
|
|
65
|
+
mailboxProgramId,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
debugLog('Outbound message path PDA:', outboundMessagePathPDA.toBase58());
|
|
69
|
+
debugLog('Sender config PDA:', senderConfigPDA.toBase58());
|
|
70
|
+
|
|
71
|
+
// ── Token accounts ──
|
|
72
|
+
const payerTokenAccount = await getAssociatedTokenAddress(
|
|
73
|
+
mint,
|
|
74
|
+
payer,
|
|
75
|
+
false,
|
|
76
|
+
tokenProgramId,
|
|
77
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
78
|
+
);
|
|
79
|
+
const treasuryTokenAccount = await getAssociatedTokenAddress(
|
|
80
|
+
mint,
|
|
81
|
+
arTreasury,
|
|
82
|
+
true,
|
|
83
|
+
tokenProgramId,
|
|
84
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
debugLog('Payer token account:', payerTokenAccount.toBase58());
|
|
88
|
+
debugLog('Treasury token account:', treasuryTokenAccount.toBase58());
|
|
89
|
+
|
|
90
|
+
// ── Balance check ──
|
|
91
|
+
const tokenBalance = await connection.getTokenAccountBalance(payerTokenAccount);
|
|
92
|
+
const userBalance = BigInt(tokenBalance.value.amount);
|
|
93
|
+
const parsedAmount = BigInt(amount);
|
|
94
|
+
if (userBalance < parsedAmount) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`Insufficient LBTC balance: have ${tokenBalance.value.uiAmountString}, need ${Number(parsedAmount) / 1e8}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Build & send with nonce retry ──
|
|
101
|
+
// The outbound_message PDA depends on global_nonce which can change between
|
|
102
|
+
// reads. Retry up to 3 times if the nonce becomes stale.
|
|
103
|
+
const MAX_NONCE_RETRIES = 3;
|
|
104
|
+
for (let attempt = 0; attempt < MAX_NONCE_RETRIES; attempt++) {
|
|
105
|
+
const freshMailboxConfig = await connection.getAccountInfo(mailboxConfigPDA);
|
|
106
|
+
if (!freshMailboxConfig) {
|
|
107
|
+
throw new Error('Mailbox config account not found');
|
|
108
|
+
}
|
|
109
|
+
// global_nonce is a u64 at offset 137; need 137 + 8 = 145 bytes
|
|
110
|
+
if (freshMailboxConfig.data.length < 145) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`Mailbox config account data too short: expected >= 145 bytes, got ${freshMailboxConfig.data.length}`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const globalNonce = freshMailboxConfig.data.readBigUInt64LE(137);
|
|
116
|
+
const nonceBuf = Buffer.alloc(8);
|
|
117
|
+
nonceBuf.writeBigUInt64BE(globalNonce);
|
|
118
|
+
|
|
119
|
+
const [outboundMessagePDA] = PublicKey.findProgramAddressSync(
|
|
120
|
+
[Buffer.from('outbound_message'), nonceBuf],
|
|
121
|
+
mailboxProgramId,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
debugLog(`Attempt ${attempt + 1}: global nonce=${globalNonce}, outbound_message=${outboundMessagePDA.toBase58()}`);
|
|
125
|
+
|
|
126
|
+
const tx = await assetRouterProgram.methods
|
|
127
|
+
.redeemForBtc(scriptPubKey, new BN(amount))
|
|
128
|
+
.accounts({
|
|
129
|
+
payer,
|
|
130
|
+
config: assetRouterConfigPDA,
|
|
131
|
+
tokenConfig: tokenConfigPDA,
|
|
132
|
+
tokenRoute: tokenRoutePDA,
|
|
133
|
+
payerTokenAccount,
|
|
134
|
+
tokenProgram: tokenProgramId,
|
|
135
|
+
mint,
|
|
136
|
+
treasuryTokenAccount,
|
|
137
|
+
messagingAuthority: messagingAuthorityPDA,
|
|
138
|
+
mailbox: mailboxProgramId,
|
|
139
|
+
mailboxConfig: mailboxConfigPDA,
|
|
140
|
+
outboundMessagePath: outboundMessagePathPDA,
|
|
141
|
+
outboundMessage: outboundMessagePDA,
|
|
142
|
+
senderConfig: senderConfigPDA,
|
|
143
|
+
treasury: mailboxTreasury,
|
|
144
|
+
systemProgram: SystemProgram.programId,
|
|
145
|
+
})
|
|
146
|
+
.transaction();
|
|
147
|
+
|
|
148
|
+
debugLog('Instruction account count:', tx.instructions[0]?.keys.length);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const { signature } = await sendAndConfirmTransaction({
|
|
152
|
+
instruction: tx,
|
|
153
|
+
connection,
|
|
154
|
+
provider,
|
|
155
|
+
debugLabel: 'Asset Router redeem_for_btc (LBTC)',
|
|
156
|
+
skipPreflight,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
debugLog('LBTC redeem_for_btc completed, signature:', signature);
|
|
160
|
+
return signature;
|
|
161
|
+
} catch (err: unknown) {
|
|
162
|
+
const isNonceError =
|
|
163
|
+
err instanceof Error &&
|
|
164
|
+
err.message.includes('0x7d6'); // ConstraintSeeds
|
|
165
|
+
if (isNonceError && attempt < MAX_NONCE_RETRIES - 1) {
|
|
166
|
+
debugLog(`Nonce stale (ConstraintSeeds), retrying...`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
throw err;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error('Failed after max nonce retries');
|
|
174
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { BTC_NATIVE_TOKEN_ADDRESS, validateAmount } from './shared';
|
|
4
|
+
|
|
5
|
+
describe('validateAmount', () => {
|
|
6
|
+
it('should accept valid positive integer strings', () => {
|
|
7
|
+
expect(() => validateAmount('1')).not.toThrow();
|
|
8
|
+
expect(() => validateAmount('100000')).not.toThrow();
|
|
9
|
+
expect(() => validateAmount('18446744073709551615')).not.toThrow(); // u64 max
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should reject zero', () => {
|
|
13
|
+
expect(() => validateAmount('0')).toThrow('greater than zero');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should reject non-numeric strings', () => {
|
|
17
|
+
expect(() => validateAmount('abc')).toThrow('positive integer string');
|
|
18
|
+
expect(() => validateAmount('1.5')).toThrow('positive integer string');
|
|
19
|
+
expect(() => validateAmount('-1')).toThrow('positive integer string');
|
|
20
|
+
expect(() => validateAmount('')).toThrow('positive integer string');
|
|
21
|
+
expect(() => validateAmount(' 100')).toThrow('positive integer string');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should reject amounts exceeding u64 max', () => {
|
|
25
|
+
expect(() => validateAmount('18446744073709551616')).toThrow('exceeds the u64 maximum');
|
|
26
|
+
expect(() => validateAmount('99999999999999999999999')).toThrow('exceeds the u64 maximum');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('BTC_NATIVE_TOKEN_ADDRESS', () => {
|
|
31
|
+
it('should be a 32-byte buffer', () => {
|
|
32
|
+
expect(BTC_NATIVE_TOKEN_ADDRESS).toBeInstanceOf(Buffer);
|
|
33
|
+
expect(BTC_NATIVE_TOKEN_ADDRESS.length).toBe(32);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should have value 1 in the last byte', () => {
|
|
37
|
+
expect(BTC_NATIVE_TOKEN_ADDRESS[31]).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should have all other bytes as zero', () => {
|
|
41
|
+
for (let i = 0; i < 31; i++) {
|
|
42
|
+
expect(BTC_NATIVE_TOKEN_ADDRESS[i]).toBe(0);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Program } from '@coral-xyz/anchor';
|
|
2
|
+
import { Env } from '@lombard.finance/sdk-common';
|
|
3
|
+
import { Connection, PublicKey } from '@solana/web3.js';
|
|
4
|
+
|
|
5
|
+
import { IConfig } from '../../const/getConfig';
|
|
6
|
+
import { ISolanaWalletProvider, SolanaNetwork } from '../../types';
|
|
7
|
+
|
|
8
|
+
// ── Types ──
|
|
9
|
+
|
|
10
|
+
export interface RedeemForBtcParams {
|
|
11
|
+
amount: string;
|
|
12
|
+
btcAddress: string;
|
|
13
|
+
/**
|
|
14
|
+
* SPL token mint to redeem. Must equal the environment’s configured LBTC or
|
|
15
|
+
* BTC.b mint; any other value fails before building a redemption transaction.
|
|
16
|
+
*/
|
|
17
|
+
tokenMint: string;
|
|
18
|
+
network: SolanaNetwork;
|
|
19
|
+
/**
|
|
20
|
+
* Optional environment override. When provided, used instead of
|
|
21
|
+
* the default `networkToEnv[network]` mapping to resolve config.
|
|
22
|
+
* Useful when multiple environments share the same Solana network
|
|
23
|
+
* (e.g. both 'dev' and 'stage' use devnet).
|
|
24
|
+
*/
|
|
25
|
+
env?: Env;
|
|
26
|
+
rpcUrl?: string;
|
|
27
|
+
debug?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Skip preflight transaction simulation before broadcast.
|
|
30
|
+
*
|
|
31
|
+
* Defaults to `false` (simulation enabled). Set to `true` if preflight
|
|
32
|
+
* simulation gives false negatives — for example, when the simulation node
|
|
33
|
+
* has not yet seen the latest global nonce for the `outbound_message` PDA,
|
|
34
|
+
* leading to a spurious `ConstraintSeeds (0x7d6)` failure even though the
|
|
35
|
+
* transaction would land correctly on-chain.
|
|
36
|
+
*/
|
|
37
|
+
skipPreflight?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type DebugLog = (...args: unknown[]) => void;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Common context shared between BTC.b and LBTC redemption flows.
|
|
44
|
+
*/
|
|
45
|
+
export interface RedeemContext {
|
|
46
|
+
provider: ISolanaWalletProvider;
|
|
47
|
+
params: RedeemForBtcParams;
|
|
48
|
+
env: Env;
|
|
49
|
+
config: IConfig;
|
|
50
|
+
connection: Connection;
|
|
51
|
+
payer: PublicKey;
|
|
52
|
+
mint: PublicKey;
|
|
53
|
+
tokenProgramId: PublicKey;
|
|
54
|
+
scriptPubKey: Buffer;
|
|
55
|
+
assetRouterProgramId: PublicKey;
|
|
56
|
+
mailboxProgramId: PublicKey;
|
|
57
|
+
solanaRoutingChainId: Buffer;
|
|
58
|
+
bitcoinRoutingChainId: Buffer;
|
|
59
|
+
assetRouterProgram: Program;
|
|
60
|
+
assetRouterConfigPDA: PublicKey;
|
|
61
|
+
mailboxConfigPDA: PublicKey;
|
|
62
|
+
arTreasury: PublicKey;
|
|
63
|
+
mailboxTreasury: PublicKey;
|
|
64
|
+
debugLog: DebugLog;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Constants ──
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* BTC native token address in Lombard protocol (to_token_address for BTC in token_route PDA).
|
|
71
|
+
* BTC is represented as 0x...01 (32 bytes, value 1).
|
|
72
|
+
*/
|
|
73
|
+
export const BTC_NATIVE_TOKEN_ADDRESS = (() => {
|
|
74
|
+
const buf = Buffer.alloc(32, 0);
|
|
75
|
+
buf[31] = 1;
|
|
76
|
+
return buf;
|
|
77
|
+
})();
|
|
78
|
+
|
|
79
|
+
// ── Helpers ──
|
|
80
|
+
|
|
81
|
+
export function validateAmount(amount: string): void {
|
|
82
|
+
const U64_MAX = 18446744073709551615n;
|
|
83
|
+
if (!/^\d+$/.test(amount)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Invalid amount "${amount}": must be a positive integer string (lamports, no decimals or signs)`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
const parsedAmount = BigInt(amount);
|
|
89
|
+
if (parsedAmount === 0n) {
|
|
90
|
+
throw new Error('Amount must be greater than zero');
|
|
91
|
+
}
|
|
92
|
+
if (parsedAmount > U64_MAX) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Amount ${amount} exceeds the u64 maximum (${U64_MAX})`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { Env } from '@lombard.finance/sdk-common';
|
|
2
|
-
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { envToNetwork } from '../../const/getConfig';
|
|
6
|
-
import {
|
|
7
|
-
Button,
|
|
8
|
-
CodeBlock,
|
|
9
|
-
ConnectButton,
|
|
10
|
-
ErrorDisplay,
|
|
11
|
-
OutputSelector,
|
|
12
|
-
ResultDisplay,
|
|
13
|
-
SectionCard,
|
|
14
|
-
} from '../../stories/components';
|
|
15
|
-
import { functionType } from '../../stories/decorators/function-type';
|
|
16
|
-
import { useConnect } from '../../stories/hooks/useConnect';
|
|
17
|
-
import { IOutput, useFetchOutputs } from '../../stories/hooks/useFetchOutputs';
|
|
18
|
-
import useQuery from '../../stories/hooks/useQuery';
|
|
19
|
-
import { claimLBTC } from './claimLBTC';
|
|
20
|
-
import { parseTransactionLogs } from './utils/parseTransactionLogs';
|
|
21
|
-
|
|
22
|
-
interface ClaimLbtcStoryArgs {
|
|
23
|
-
environment: Env;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const StoryView = ({ environment }: ClaimLbtcStoryArgs) => {
|
|
27
|
-
const network = envToNetwork[environment];
|
|
28
|
-
const [selectedOutput, setSelectedOutput] = useState<IOutput | null>(null);
|
|
29
|
-
const [transactionLogs, setTransactionLogs] = useState<string[] | null>(null);
|
|
30
|
-
|
|
31
|
-
const {
|
|
32
|
-
data: connectionData,
|
|
33
|
-
error: connectError,
|
|
34
|
-
isLoading: isConnecting,
|
|
35
|
-
connect,
|
|
36
|
-
disconnect,
|
|
37
|
-
} = useConnect();
|
|
38
|
-
const isConnected = !!connectionData;
|
|
39
|
-
const address = connectionData?.address;
|
|
40
|
-
const provider = connectionData?.provider;
|
|
41
|
-
|
|
42
|
-
const { refetchOutputs } = useFetchOutputs({
|
|
43
|
-
address: address ?? undefined,
|
|
44
|
-
environment,
|
|
45
|
-
isConnected: isConnected,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const request = async () => {
|
|
49
|
-
if (!provider || !address) throw new Error('Wallet not connected.');
|
|
50
|
-
if (!selectedOutput) throw new Error('Please select an output to claim.');
|
|
51
|
-
if (!selectedOutput.raw_payload)
|
|
52
|
-
throw new Error('Selected output has no raw_payload.');
|
|
53
|
-
if (!selectedOutput.proof)
|
|
54
|
-
throw new Error('Selected output has no proof (required for LBTC).');
|
|
55
|
-
|
|
56
|
-
setTransactionLogs(null);
|
|
57
|
-
try {
|
|
58
|
-
const txHash = await claimLBTC(provider, {
|
|
59
|
-
recipientAddress: address,
|
|
60
|
-
amount: selectedOutput.value,
|
|
61
|
-
network,
|
|
62
|
-
proofSignature: selectedOutput.proof,
|
|
63
|
-
rawPayload: selectedOutput.raw_payload,
|
|
64
|
-
debug: true,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
refetchOutputs();
|
|
68
|
-
setSelectedOutput(null);
|
|
69
|
-
return txHash;
|
|
70
|
-
} catch (err: unknown) {
|
|
71
|
-
const { errorMessage, errorLogs } = parseTransactionLogs(err);
|
|
72
|
-
setTransactionLogs(errorLogs);
|
|
73
|
-
throw new Error(errorMessage);
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const {
|
|
78
|
-
data: result,
|
|
79
|
-
error,
|
|
80
|
-
isLoading,
|
|
81
|
-
refetch: handleClaim,
|
|
82
|
-
} = useQuery(
|
|
83
|
-
request,
|
|
84
|
-
[provider, address, selectedOutput, environment, refetchOutputs],
|
|
85
|
-
false,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
setSelectedOutput(null);
|
|
90
|
-
setTransactionLogs(null);
|
|
91
|
-
}, [isConnected, address, environment]);
|
|
92
|
-
|
|
93
|
-
return (
|
|
94
|
-
<>
|
|
95
|
-
<ConnectButton
|
|
96
|
-
connect={connect}
|
|
97
|
-
disconnect={disconnect}
|
|
98
|
-
isConnected={isConnected}
|
|
99
|
-
isLoading={isConnecting}
|
|
100
|
-
error={connectError}
|
|
101
|
-
network={network}
|
|
102
|
-
walletName={connectionData?.walletName}
|
|
103
|
-
address={connectionData?.address}
|
|
104
|
-
/>
|
|
105
|
-
|
|
106
|
-
{isConnected && address && provider && (
|
|
107
|
-
<>
|
|
108
|
-
<SectionCard title="Available Bitcoin Outputs">
|
|
109
|
-
<OutputSelector
|
|
110
|
-
address={address}
|
|
111
|
-
environment={environment}
|
|
112
|
-
isConnected={isConnected}
|
|
113
|
-
selectedOutput={selectedOutput}
|
|
114
|
-
onOutputSelect={setSelectedOutput}
|
|
115
|
-
/>
|
|
116
|
-
</SectionCard>
|
|
117
|
-
|
|
118
|
-
<div className="d-grid gap-2 mb-4">
|
|
119
|
-
<Button
|
|
120
|
-
primary
|
|
121
|
-
size="large"
|
|
122
|
-
onClick={handleClaim}
|
|
123
|
-
isLoading={isLoading}
|
|
124
|
-
disabled={
|
|
125
|
-
isLoading ||
|
|
126
|
-
!selectedOutput ||
|
|
127
|
-
!selectedOutput.raw_payload
|
|
128
|
-
}
|
|
129
|
-
actionName="claimLBTC"
|
|
130
|
-
/>
|
|
131
|
-
{selectedOutput && !selectedOutput.raw_payload && (
|
|
132
|
-
<p className="text-warning small mt-1 mb-0">
|
|
133
|
-
Output is still awaiting notarization. Use "Refresh Outputs" to
|
|
134
|
-
check for updates.
|
|
135
|
-
</p>
|
|
136
|
-
)}
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
{result && (
|
|
140
|
-
<ResultDisplay
|
|
141
|
-
result={result}
|
|
142
|
-
title="Claim Transaction Hash"
|
|
143
|
-
successMessage="LBTC claimed!"
|
|
144
|
-
/>
|
|
145
|
-
)}
|
|
146
|
-
|
|
147
|
-
{error && (
|
|
148
|
-
<ErrorDisplay error={error || connectError} title="Claim Error" />
|
|
149
|
-
)}
|
|
150
|
-
|
|
151
|
-
{transactionLogs && transactionLogs.length > 0 && (
|
|
152
|
-
<SectionCard title="Transaction Logs (Debug)">
|
|
153
|
-
<CodeBlock text={transactionLogs.join('\n')} />
|
|
154
|
-
</SectionCard>
|
|
155
|
-
)}
|
|
156
|
-
</>
|
|
157
|
-
)}
|
|
158
|
-
</>
|
|
159
|
-
);
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const meta: Meta<typeof StoryView> = {
|
|
163
|
-
title: 'write/claimLBTC',
|
|
164
|
-
component: StoryView,
|
|
165
|
-
tags: ['autodocs'],
|
|
166
|
-
decorators: [functionType('write')],
|
|
167
|
-
parameters: {
|
|
168
|
-
docs: {
|
|
169
|
-
description: {
|
|
170
|
-
component: `Claim LBTC tokens on Solana.
|
|
171
|
-
|
|
172
|
-
Uses the legacy 3-step on-chain flow (\`claimLBTC\`):
|
|
173
|
-
1. Create Mint Payload → 2. Post Mint Signatures → 3. Mint From Payload`,
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
args: {
|
|
178
|
-
environment: Env.stage,
|
|
179
|
-
},
|
|
180
|
-
argTypes: {
|
|
181
|
-
environment: {
|
|
182
|
-
control: { type: 'select' },
|
|
183
|
-
options: Object.values(Env),
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
};
|
|
187
|
-
export default meta;
|
|
188
|
-
|
|
189
|
-
type Story = StoryObj<typeof meta>;
|