@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Solana Service Implementation
|
|
3
3
|
*
|
|
4
|
-
* Provides Solana-specific operations for LBTC destination signing and
|
|
4
|
+
* Provides Solana-specific operations for LBTC destination signing, redeem and deposit.
|
|
5
5
|
*
|
|
6
6
|
* @module services/SolanaServiceImpl
|
|
7
7
|
*/
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
import type { Env, SolanaService } from '@lombard.finance/sdk-common';
|
|
10
10
|
|
|
11
11
|
import type { ISolanaWalletProvider, SolanaNetwork } from '../types';
|
|
12
|
+
import { deposit } from '../web3Sdk/deposit/deposit';
|
|
13
|
+
import { redeem } from '../web3Sdk/redeem/redeem';
|
|
12
14
|
import { redeemForBtc } from '../web3Sdk/redeemToken/redeemForBtc';
|
|
13
15
|
import { signLbtcDestinationAddrSolana } from '../web3Sdk/signLbtcDestinationAddrSolana';
|
|
14
|
-
import { unstakeLBTC } from '../web3Sdk/unstakeLBTC/unstakeLBTC';
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* Provider resolver function type
|
|
@@ -41,46 +42,81 @@ export class SolanaServiceImpl implements SolanaService {
|
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
|
-
*
|
|
45
|
+
* Redeem BTC.b or LBTC on Solana to receive BTC
|
|
45
46
|
*
|
|
46
|
-
* Burns
|
|
47
|
+
* Burns the source token and sends a GMP message to trigger a BTC payout.
|
|
47
48
|
*/
|
|
48
|
-
async
|
|
49
|
+
async redeemForBtc(args: {
|
|
49
50
|
amount: string;
|
|
50
51
|
btcAddress: string;
|
|
51
52
|
network: string;
|
|
52
|
-
|
|
53
|
+
env?: Env;
|
|
54
|
+
tokenMint: string;
|
|
55
|
+
}): Promise<{ signature: string }> {
|
|
53
56
|
const provider = (await this.getProvider()) as ISolanaWalletProvider;
|
|
54
57
|
|
|
55
|
-
const
|
|
58
|
+
const signature = await redeemForBtc(provider, {
|
|
56
59
|
amount: args.amount,
|
|
57
60
|
btcAddress: args.btcAddress,
|
|
58
61
|
network: args.network as SolanaNetwork,
|
|
62
|
+
env: args.env,
|
|
63
|
+
tokenMint: args.tokenMint,
|
|
59
64
|
});
|
|
60
65
|
|
|
61
|
-
return {
|
|
66
|
+
return { signature };
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* Burns BTC.b and sends a GMP message to trigger a BTC payout.
|
|
70
|
+
* Generic redeem via Asset Router (default: LBTC → BTC.b)
|
|
68
71
|
*/
|
|
69
|
-
async
|
|
72
|
+
async redeem(args: {
|
|
70
73
|
amount: string;
|
|
71
|
-
|
|
74
|
+
recipient: string;
|
|
72
75
|
network: string;
|
|
73
76
|
env?: Env;
|
|
74
|
-
|
|
77
|
+
tokenMint?: string;
|
|
78
|
+
toLchainId?: string;
|
|
79
|
+
toTokenAddress?: string;
|
|
80
|
+
}): Promise<{ signature: string }> {
|
|
75
81
|
const provider = (await this.getProvider()) as ISolanaWalletProvider;
|
|
76
82
|
|
|
77
|
-
const
|
|
83
|
+
const signature = await redeem(provider, {
|
|
78
84
|
amount: args.amount,
|
|
79
|
-
|
|
85
|
+
recipient: args.recipient,
|
|
86
|
+
network: args.network as SolanaNetwork,
|
|
87
|
+
env: args.env,
|
|
88
|
+
tokenMint: args.tokenMint,
|
|
89
|
+
toLchainId: args.toLchainId,
|
|
90
|
+
toTokenAddress: args.toTokenAddress,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return { signature };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Deposit via Asset Router (default: BTC.b → LBTC)
|
|
98
|
+
*/
|
|
99
|
+
async deposit(args: {
|
|
100
|
+
amount: string;
|
|
101
|
+
recipient: string;
|
|
102
|
+
network: string;
|
|
103
|
+
env?: Env;
|
|
104
|
+
sourceTokenMint?: string;
|
|
105
|
+
toLchainId?: string;
|
|
106
|
+
toTokenAddress?: string;
|
|
107
|
+
}): Promise<{ signature: string }> {
|
|
108
|
+
const provider = (await this.getProvider()) as ISolanaWalletProvider;
|
|
109
|
+
|
|
110
|
+
const signature = await deposit(provider, {
|
|
111
|
+
amount: args.amount,
|
|
112
|
+
recipient: args.recipient,
|
|
80
113
|
network: args.network as SolanaNetwork,
|
|
81
114
|
env: args.env,
|
|
115
|
+
sourceTokenMint: args.sourceTokenMint,
|
|
116
|
+
toLchainId: args.toLchainId,
|
|
117
|
+
toTokenAddress: args.toTokenAddress,
|
|
82
118
|
});
|
|
83
119
|
|
|
84
|
-
return {
|
|
120
|
+
return { signature };
|
|
85
121
|
}
|
|
86
122
|
}
|
|
@@ -41,6 +41,7 @@ export const OutputSelector: React.FC<OutputSelectorProps> = ({
|
|
|
41
41
|
case 'NOTARIZATION_STATUS_SESSION_APPROVED':
|
|
42
42
|
return 'Ready to mint';
|
|
43
43
|
case 'NOTARIZATION_STATUS_PENDING':
|
|
44
|
+
case 'NOTARIZATION_STATUS_GMP_PENDING':
|
|
44
45
|
return 'Pending';
|
|
45
46
|
case 'NOTARIZATION_STATUS_SUBMITTED':
|
|
46
47
|
return 'Submitted';
|
package/src/types/errors.ts
CHANGED
|
@@ -40,6 +40,8 @@ export enum ErrorCode {
|
|
|
40
40
|
INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS',
|
|
41
41
|
SIGNING_REJECTED = 'SIGNING_REJECTED',
|
|
42
42
|
CLAIM_REJECTED = 'CLAIM_REJECTED',
|
|
43
|
+
DEPOSIT_REJECTED = 'DEPOSIT_REJECTED',
|
|
44
|
+
REDEEM_REJECTED = 'REDEEM_REJECTED',
|
|
43
45
|
UNSTAKE_REJECTED = 'UNSTAKE_REJECTED',
|
|
44
46
|
|
|
45
47
|
// Network errors
|
|
@@ -25,33 +25,26 @@ export function createDebugLogger(options: CreateDebugLoggerOptions = {}) {
|
|
|
25
25
|
const { debug = false, prefix = '' } = options;
|
|
26
26
|
const logs: string[] = [];
|
|
27
27
|
|
|
28
|
-
// Return the actual logger function
|
|
29
28
|
const debugLog = (...args: unknown[]): void => {
|
|
30
|
-
// Only proceed if debugging is enabled
|
|
31
|
-
if (!debug) {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Format arguments: stringify objects, keep others as strings
|
|
36
29
|
const formattedArgs = args.map(arg =>
|
|
37
30
|
typeof arg === 'object' && arg !== null
|
|
38
31
|
? JSON.stringify(arg)
|
|
39
32
|
: String(arg),
|
|
40
33
|
);
|
|
41
34
|
|
|
42
|
-
// Construct the final log message with optional prefix
|
|
43
35
|
const logMessage = prefix
|
|
44
36
|
? `${prefix} ${formattedArgs.join(' ')}`
|
|
45
37
|
: formattedArgs.join(' ');
|
|
46
38
|
|
|
47
|
-
|
|
48
|
-
console.log(logMessage);
|
|
49
|
-
};
|
|
39
|
+
logs.push(logMessage);
|
|
50
40
|
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
if (debug) {
|
|
42
|
+
console.log(logMessage);
|
|
43
|
+
}
|
|
53
44
|
};
|
|
54
45
|
|
|
46
|
+
const printLogs = (): string => logs.join('\n');
|
|
47
|
+
|
|
55
48
|
return { debugLog, printLogs };
|
|
56
49
|
}
|
|
57
50
|
|
package/src/utils/errors.ts
CHANGED
|
@@ -11,6 +11,8 @@ export enum ErrorCode {
|
|
|
11
11
|
CONNECTION_ERROR = 'CONNECTION_ERROR',
|
|
12
12
|
CONNECTION_REJECTED = 'CONNECTION_REJECTED',
|
|
13
13
|
CONNECTION_TIMEOUT = 'CONNECTION_TIMEOUT',
|
|
14
|
+
DEPOSIT_REJECTED = 'DEPOSIT_REJECTED',
|
|
15
|
+
REDEEM_REJECTED = 'REDEEM_REJECTED',
|
|
14
16
|
INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS',
|
|
15
17
|
INVALID_ADDRESS = 'INVALID_ADDRESS',
|
|
16
18
|
INVALID_AMOUNT = 'INVALID_AMOUNT',
|
|
@@ -51,11 +51,13 @@ export async function createOrGetAssociatedTokenAccount({
|
|
|
51
51
|
connection,
|
|
52
52
|
ownerAddress,
|
|
53
53
|
mintAddress,
|
|
54
|
+
allowOwnerOffCurve = false,
|
|
54
55
|
}: {
|
|
55
56
|
provider: ISolanaWalletProvider;
|
|
56
57
|
connection: Connection;
|
|
57
58
|
ownerAddress: string;
|
|
58
59
|
mintAddress: string;
|
|
60
|
+
allowOwnerOffCurve?: boolean;
|
|
59
61
|
}): Promise<string> {
|
|
60
62
|
const mintPubkey = new PublicKey(mintAddress);
|
|
61
63
|
const ownerPubkey = new PublicKey(ownerAddress);
|
|
@@ -65,7 +67,7 @@ export async function createOrGetAssociatedTokenAccount({
|
|
|
65
67
|
const associatedTokenAddress = await getAssociatedTokenAddress(
|
|
66
68
|
mintPubkey,
|
|
67
69
|
ownerPubkey,
|
|
68
|
-
|
|
70
|
+
allowOwnerOffCurve,
|
|
69
71
|
tokenProgramId,
|
|
70
72
|
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
71
73
|
);
|
|
@@ -3,7 +3,7 @@ import { PublicKey, SystemProgram } from '@solana/web3.js';
|
|
|
3
3
|
|
|
4
4
|
import { sendAndConfirmTransaction } from '../../utils';
|
|
5
5
|
import { createOrGetAssociatedTokenAccount } from '../../utils/tokenAccount';
|
|
6
|
-
import { ALREADY_MINTED_TX_HASH } from '
|
|
6
|
+
import { ALREADY_MINTED_TX_HASH } from './constants';
|
|
7
7
|
import {
|
|
8
8
|
assertBtcbDepositRecipientMatchesWallet,
|
|
9
9
|
ClaimContext,
|
|
@@ -80,57 +80,66 @@ export async function claimBtcbFromPayload(ctx: ClaimContext): Promise<string> {
|
|
|
80
80
|
connection,
|
|
81
81
|
ownerAddress: params.recipientAddress,
|
|
82
82
|
mintAddress: mint.toBase58(),
|
|
83
|
+
allowOwnerOffCurve: true,
|
|
83
84
|
});
|
|
84
85
|
|
|
85
|
-
// Build mint_from_payload instruction
|
|
86
|
+
// Build mint_from_payload instruction.
|
|
87
|
+
// Optional bascule accounts must be passed explicitly (null when disabled),
|
|
88
|
+
// otherwise Anchor builder throws "Account <x> not provided".
|
|
86
89
|
debugLog('mint_from_payload...');
|
|
87
90
|
const mintPayloadArray = Array.from(payloadBytes);
|
|
88
|
-
const mintIx = await assetRouterProgram.methods
|
|
89
|
-
.mintFromPayload(mintPayloadArray, payloadHashArray)
|
|
90
|
-
.accounts({
|
|
91
|
-
payer: provider.publicKey,
|
|
92
|
-
config: assetRouterConfigPDA,
|
|
93
|
-
tokenProgram: tokenProgramId,
|
|
94
|
-
recipient: recipientTokenAccount,
|
|
95
|
-
mint,
|
|
96
|
-
mintAuthority,
|
|
97
|
-
tokenAuthority: tokenAuthorityPDA,
|
|
98
|
-
consortiumValidatedPayload: validatedPayloadPDA,
|
|
99
|
-
depositPayloadSpent: depositPayloadSpentPDA,
|
|
100
|
-
systemProgram: SystemProgram.programId,
|
|
101
|
-
})
|
|
102
|
-
.instruction();
|
|
103
91
|
|
|
104
|
-
|
|
92
|
+
let basculeValidatorPDA: PublicKey | undefined;
|
|
93
|
+
let basculeDataPDA: PublicKey | undefined;
|
|
94
|
+
let basculeDepositPDA: PublicKey | undefined;
|
|
95
|
+
|
|
105
96
|
if (effectiveBasculeProgramId) {
|
|
106
|
-
|
|
97
|
+
[basculeValidatorPDA] = PublicKey.findProgramAddressSync(
|
|
107
98
|
[Buffer.from('bascule_validator')],
|
|
108
99
|
assetRouterProgramId,
|
|
109
100
|
);
|
|
110
|
-
|
|
101
|
+
[basculeDataPDA] = PublicKey.findProgramAddressSync(
|
|
111
102
|
[Buffer.from('bascule')],
|
|
112
103
|
effectiveBasculeProgramId,
|
|
113
104
|
);
|
|
114
105
|
const depositId = computeDepositIdFromPayload(payloadBytes);
|
|
115
106
|
debugLog('Deposit ID:', Buffer.from(depositId).toString('hex'));
|
|
116
107
|
|
|
117
|
-
|
|
108
|
+
[basculeDepositPDA] = PublicKey.findProgramAddressSync(
|
|
118
109
|
[Buffer.from('deposit'), depositId],
|
|
119
110
|
effectiveBasculeProgramId,
|
|
120
111
|
);
|
|
121
112
|
|
|
113
|
+
debugLog('Asset router program:', assetRouterProgramId.toBase58());
|
|
114
|
+
debugLog('Bascule program:', effectiveBasculeProgramId.toBase58());
|
|
122
115
|
debugLog('Bascule validator PDA:', basculeValidatorPDA.toBase58());
|
|
123
116
|
debugLog('Bascule data PDA:', basculeDataPDA.toBase58());
|
|
124
117
|
debugLog('Bascule deposit PDA:', basculeDepositPDA.toBase58());
|
|
125
|
-
|
|
126
|
-
mintIx.keys.push(
|
|
127
|
-
{ pubkey: basculeValidatorPDA, isSigner: false, isWritable: false },
|
|
128
|
-
{ pubkey: effectiveBasculeProgramId, isSigner: false, isWritable: false },
|
|
129
|
-
{ pubkey: basculeDataPDA, isSigner: false, isWritable: true },
|
|
130
|
-
{ pubkey: basculeDepositPDA, isSigner: false, isWritable: true },
|
|
131
|
-
);
|
|
132
118
|
}
|
|
133
119
|
|
|
120
|
+
// Anchor optional accounts: when disabled, pass the program's own ID as a
|
|
121
|
+
// "None" sentinel — this is the convention anchor-client uses for optional accounts.
|
|
122
|
+
const basculeSentinel = assetRouterProgramId;
|
|
123
|
+
const mintIx = await assetRouterProgram.methods
|
|
124
|
+
.mintFromPayload(mintPayloadArray, payloadHashArray)
|
|
125
|
+
.accountsPartial({
|
|
126
|
+
payer: provider.publicKey,
|
|
127
|
+
config: assetRouterConfigPDA,
|
|
128
|
+
tokenProgram: tokenProgramId,
|
|
129
|
+
recipient: recipientTokenAccount,
|
|
130
|
+
mint,
|
|
131
|
+
mintAuthority,
|
|
132
|
+
tokenAuthority: tokenAuthorityPDA,
|
|
133
|
+
consortiumValidatedPayload: validatedPayloadPDA,
|
|
134
|
+
depositPayloadSpent: depositPayloadSpentPDA,
|
|
135
|
+
systemProgram: SystemProgram.programId,
|
|
136
|
+
basculeValidator: basculeValidatorPDA ?? basculeSentinel,
|
|
137
|
+
basculeProgram: effectiveBasculeProgramId ?? basculeSentinel,
|
|
138
|
+
basculeData: basculeDataPDA ?? basculeSentinel,
|
|
139
|
+
basculeDeposit: basculeDepositPDA ?? basculeSentinel,
|
|
140
|
+
})
|
|
141
|
+
.instruction();
|
|
142
|
+
|
|
134
143
|
debugLog('Instruction account count:', mintIx.keys.length);
|
|
135
144
|
|
|
136
145
|
const { signature } = await sendAndConfirmTransaction({
|
|
@@ -1,13 +1,44 @@
|
|
|
1
1
|
import { Program } from '@coral-xyz/anchor';
|
|
2
2
|
import { getMint } from '@solana/spl-token';
|
|
3
3
|
import { PublicKey, SystemProgram } from '@solana/web3.js';
|
|
4
|
+
import { keccak256 } from 'js-sha3';
|
|
4
5
|
|
|
5
6
|
import { getMailboxIdl } from '../../idl/getMailboxIdl';
|
|
6
7
|
import { sendAndConfirmTransaction } from '../../utils';
|
|
7
8
|
import { createOrGetAssociatedTokenAccount } from '../../utils/tokenAccount';
|
|
8
|
-
import { ALREADY_MINTED_TX_HASH } from '
|
|
9
|
+
import { ALREADY_MINTED_TX_HASH } from './constants';
|
|
9
10
|
import { ClaimContext, executeConsortiumSession } from './shared';
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Mirrors bascule_gmp MintMessage.mint_id():
|
|
14
|
+
* keccak256( nonce_u256_be || chain_id(32) || recipient(32) || token(32) || amount_u256_be )
|
|
15
|
+
* Payload: nonce u256 at [36:68], token at [232:264], recipient at [264:296], amount u256 at [296:328].
|
|
16
|
+
* Only the low 8 bytes of each u256 are used (placed in the last 8 bytes of the 32-byte slot).
|
|
17
|
+
*/
|
|
18
|
+
function computeBasculeGmpMintId(
|
|
19
|
+
payload: Buffer,
|
|
20
|
+
chainId: Buffer,
|
|
21
|
+
): Uint8Array {
|
|
22
|
+
if (payload.length < 328) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`payload too short for bascule_gmp mint_id: ${payload.length} bytes`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
if (chainId.length !== 32) {
|
|
28
|
+
throw new Error(`chain_id must be 32 bytes, got ${chainId.length}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const data = Buffer.alloc(160);
|
|
32
|
+
payload.copy(data, 24, 60, 68);
|
|
33
|
+
chainId.copy(data, 32);
|
|
34
|
+
payload.copy(data, 64, 264, 296);
|
|
35
|
+
payload.copy(data, 96, 232, 264);
|
|
36
|
+
payload.copy(data, 152, 320, 328);
|
|
37
|
+
|
|
38
|
+
const hash = keccak256(new Uint8Array(data));
|
|
39
|
+
return new Uint8Array(Buffer.from(hash, 'hex'));
|
|
40
|
+
}
|
|
41
|
+
|
|
11
42
|
/**
|
|
12
43
|
* LBTC GMP flow via Consortium + Mailbox + Asset Router gmp_receive.
|
|
13
44
|
*
|
|
@@ -25,9 +56,9 @@ export async function claimLbtcGmp(ctx: ClaimContext): Promise<string> {
|
|
|
25
56
|
validatedPayloadPDA, arConfig, debugLog,
|
|
26
57
|
} = ctx;
|
|
27
58
|
|
|
28
|
-
if (payloadBytes.length <
|
|
59
|
+
if (payloadBytes.length < 328) {
|
|
29
60
|
throw new Error(
|
|
30
|
-
`LBTC GMP payload too short: expected >=
|
|
61
|
+
`LBTC GMP payload too short: expected >= 328 bytes, got ${payloadBytes.length}`,
|
|
31
62
|
);
|
|
32
63
|
}
|
|
33
64
|
|
|
@@ -74,7 +105,7 @@ export async function claimLbtcGmp(ctx: ClaimContext): Promise<string> {
|
|
|
74
105
|
connection,
|
|
75
106
|
provider,
|
|
76
107
|
debugLabel: 'Consortium post_session_payload',
|
|
77
|
-
skipPreflight: params.skipPreflight ??
|
|
108
|
+
skipPreflight: params.skipPreflight ?? true,
|
|
78
109
|
});
|
|
79
110
|
debugLog('post_session_payload completed');
|
|
80
111
|
}
|
|
@@ -133,7 +164,7 @@ export async function claimLbtcGmp(ctx: ClaimContext): Promise<string> {
|
|
|
133
164
|
connection,
|
|
134
165
|
provider,
|
|
135
166
|
debugLabel: 'Mailbox deliver_message',
|
|
136
|
-
skipPreflight: params.skipPreflight ??
|
|
167
|
+
skipPreflight: params.skipPreflight ?? true,
|
|
137
168
|
});
|
|
138
169
|
debugLog('deliver_message completed');
|
|
139
170
|
}
|
|
@@ -169,6 +200,7 @@ export async function claimLbtcGmp(ctx: ClaimContext): Promise<string> {
|
|
|
169
200
|
connection,
|
|
170
201
|
ownerAddress: tokenRecipient.toBase58(),
|
|
171
202
|
mintAddress: mint.toBase58(),
|
|
203
|
+
allowOwnerOffCurve: true,
|
|
172
204
|
});
|
|
173
205
|
|
|
174
206
|
// Build handle_message instruction
|
|
@@ -210,16 +242,31 @@ export async function claimLbtcGmp(ctx: ClaimContext): Promise<string> {
|
|
|
210
242
|
effectiveBasculeGmpProgramId,
|
|
211
243
|
);
|
|
212
244
|
const [basculeGmpAccountRolesPDA] = PublicKey.findProgramAddressSync(
|
|
213
|
-
[Buffer.from('account_roles'),
|
|
245
|
+
[Buffer.from('account_roles'), basculeValidatorPDA.toBytes()],
|
|
214
246
|
effectiveBasculeGmpProgramId,
|
|
215
247
|
);
|
|
248
|
+
|
|
249
|
+
if (!config.solanaRoutingChainId) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Solana routing chain ID not configured for network: ${params.network}`,
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const solanaChainId = Buffer.from(config.solanaRoutingChainId, 'hex');
|
|
255
|
+
const mintId = computeBasculeGmpMintId(payloadBytes, solanaChainId);
|
|
216
256
|
const [basculeGmpMintPayloadPDA] = PublicKey.findProgramAddressSync(
|
|
217
|
-
[Buffer.from('mint_payload'),
|
|
257
|
+
[Buffer.from('mint_payload'), Buffer.from(mintId)],
|
|
218
258
|
effectiveBasculeGmpProgramId,
|
|
219
259
|
);
|
|
220
260
|
|
|
261
|
+
debugLog('Asset router program:', assetRouterProgramId.toBase58());
|
|
262
|
+
debugLog('Bascule GMP program:', effectiveBasculeGmpProgramId.toBase58());
|
|
263
|
+
debugLog('Bascule validator PDA:', basculeValidatorPDA.toBase58());
|
|
264
|
+
debugLog('Bascule GMP config PDA:', basculeGmpConfigPDA.toBase58());
|
|
265
|
+
debugLog('Bascule GMP account roles PDA:', basculeGmpAccountRolesPDA.toBase58());
|
|
266
|
+
debugLog('Bascule GMP mint payload PDA:', basculeGmpMintPayloadPDA.toBase58());
|
|
267
|
+
|
|
221
268
|
handleIx.keys.push(
|
|
222
|
-
{ pubkey: basculeValidatorPDA, isSigner: false, isWritable:
|
|
269
|
+
{ pubkey: basculeValidatorPDA, isSigner: false, isWritable: true },
|
|
223
270
|
{ pubkey: effectiveBasculeGmpProgramId, isSigner: false, isWritable: false },
|
|
224
271
|
{ pubkey: basculeGmpConfigPDA, isSigner: false, isWritable: false },
|
|
225
272
|
{ pubkey: basculeGmpAccountRolesPDA, isSigner: false, isWritable: false },
|
|
@@ -235,6 +282,17 @@ export async function claimLbtcGmp(ctx: ClaimContext): Promise<string> {
|
|
|
235
282
|
}
|
|
236
283
|
|
|
237
284
|
debugLog('handle_message account count:', handleIx.keys.length);
|
|
285
|
+
debugLog(
|
|
286
|
+
'handle_message program:', handleIx.programId.toBase58(),
|
|
287
|
+
);
|
|
288
|
+
debugLog(
|
|
289
|
+
'handle_message data (hex):', Buffer.from(handleIx.data).toString('hex'),
|
|
290
|
+
);
|
|
291
|
+
handleIx.keys.forEach((k, i) => {
|
|
292
|
+
debugLog(
|
|
293
|
+
` [${i}] ${k.pubkey.toBase58()} signer=${k.isSigner} writable=${k.isWritable}`,
|
|
294
|
+
);
|
|
295
|
+
});
|
|
238
296
|
|
|
239
297
|
const { signature } = await sendAndConfirmTransaction({
|
|
240
298
|
instruction: handleIx,
|
|
@@ -206,8 +206,8 @@ const meta: Meta<typeof StoryView> = {
|
|
|
206
206
|
3. Wait for backend to notarize the deposit (Consortium validation)
|
|
207
207
|
4. Call \`claimToken\` to mint tokens via Asset Router's \`mint_from_payload\`
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
The Consortium validation is handled entirely by the backend, so the on-chain flow
|
|
210
|
+
is a single transaction.`,
|
|
211
211
|
},
|
|
212
212
|
},
|
|
213
213
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { AnchorProvider, Program, setProvider } from '@coral-xyz/anchor';
|
|
2
2
|
import { PublicKey } from '@solana/web3.js';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { DEFAULT_ENV, getConfig } from '../../const/getConfig';
|
|
5
5
|
import { getConnection } from '../../const/rpcUrls';
|
|
6
6
|
import { getAssetRouterIdl } from '../../idl/getAssetRouterIdl';
|
|
7
7
|
import { getConsortiumIdl } from '../../idl/getConsortiumIdl';
|
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
ClaimTokenParams,
|
|
15
15
|
computePayloadHash,
|
|
16
16
|
DEPOSIT_SELECTOR_V1,
|
|
17
|
+
fetchAssetRouterConfig,
|
|
17
18
|
fetchCurrentEpoch,
|
|
18
19
|
getConsortiumConfigPDA,
|
|
19
20
|
getConsortiumSessionPDA,
|
|
20
21
|
GMP_MESSAGE_V1_SELECTOR,
|
|
21
|
-
parseAssetRouterConfig,
|
|
22
22
|
} from './shared';
|
|
23
23
|
|
|
24
24
|
export type { ClaimTokenParams } from './shared';
|
|
@@ -34,7 +34,7 @@ export async function claimToken(
|
|
|
34
34
|
provider: ISolanaWalletProvider,
|
|
35
35
|
params: ClaimTokenParams,
|
|
36
36
|
): Promise<string> {
|
|
37
|
-
const { network, env
|
|
37
|
+
const { network, env = DEFAULT_ENV, rawPayload, rpcUrl, debug = false } = params;
|
|
38
38
|
const { debugLog, printLogs } = createDebugLogger({ debug });
|
|
39
39
|
|
|
40
40
|
try {
|
|
@@ -42,8 +42,8 @@ export async function claimToken(
|
|
|
42
42
|
throw new Error('Wallet not found');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const env = envOverride ?? networkToEnv[network];
|
|
46
45
|
const config = getConfig(env);
|
|
46
|
+
|
|
47
47
|
if (!config.assetRouter) {
|
|
48
48
|
throw new Error(`Asset Router not configured for network: ${network}`);
|
|
49
49
|
}
|
|
@@ -72,7 +72,10 @@ export async function claimToken(
|
|
|
72
72
|
const consortiumProgramId = new PublicKey(config.consortium);
|
|
73
73
|
|
|
74
74
|
// Parse payload
|
|
75
|
-
const
|
|
75
|
+
const cleanPayload = rawPayload.startsWith('0x')
|
|
76
|
+
? rawPayload.slice(2)
|
|
77
|
+
: rawPayload;
|
|
78
|
+
const payloadBytes = Buffer.from(cleanPayload, 'hex');
|
|
76
79
|
if (payloadBytes.length < 4) {
|
|
77
80
|
throw new Error(`Payload too short: ${payloadBytes.length} bytes`);
|
|
78
81
|
}
|
|
@@ -92,7 +95,7 @@ export async function claimToken(
|
|
|
92
95
|
|
|
93
96
|
// Fetch current epoch from on-chain consortium config
|
|
94
97
|
const currentEpoch = await fetchCurrentEpoch(
|
|
95
|
-
|
|
98
|
+
consortiumProgram,
|
|
96
99
|
consortiumConfigPDA,
|
|
97
100
|
);
|
|
98
101
|
debugLog('Current consortium epoch:', currentEpoch.toString());
|
|
@@ -116,18 +119,19 @@ export async function claimToken(
|
|
|
116
119
|
assetRouterProgramId,
|
|
117
120
|
);
|
|
118
121
|
|
|
119
|
-
// Read on-chain Asset Router config
|
|
122
|
+
// Read on-chain Asset Router config via Anchor (IDL-based deserialization)
|
|
120
123
|
debugLog('Asset Router program ID:', assetRouterProgramId.toBase58());
|
|
121
124
|
debugLog('Asset Router config PDA:', assetRouterConfigPDA.toBase58());
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
const arConfig = await fetchAssetRouterConfig(
|
|
126
|
+
assetRouterProgram,
|
|
127
|
+
assetRouterConfigPDA,
|
|
128
|
+
);
|
|
129
|
+
debugLog(
|
|
130
|
+
'Asset Router config — paused:', arConfig.paused,
|
|
131
|
+
'nativeMint:', arConfig.nativeMint.toBase58(),
|
|
132
|
+
'bascule:', arConfig.basculeProgramId?.toBase58() ?? 'null',
|
|
133
|
+
'basculeGmp:', arConfig.basculeGmpProgramId?.toBase58() ?? 'null',
|
|
134
|
+
);
|
|
131
135
|
|
|
132
136
|
if (arConfig.paused) {
|
|
133
137
|
throw new Error('Asset Router contract is paused');
|