@wireio/stake 1.5.69 → 1.6.69
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 +5 -0
- package/lib/stake.browser.js +282 -8
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +44 -2
- package/lib/stake.js +493 -167
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +282 -8
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/networks/solana/clients/outpost.client.ts +20 -2
- package/src/networks/solana/constants.ts +1 -1
- package/src/networks/solana/solana.ts +96 -6
- package/src/status.ts +301 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -112,8 +112,26 @@ export class OutpostClient {
|
|
|
112
112
|
* Internal helper: get raw token balance (BN) for a given ATA.
|
|
113
113
|
*/
|
|
114
114
|
private async getTokenBalance(ata: PublicKey): Promise<BN> {
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
try {
|
|
116
|
+
const bal = await this.connection.getTokenAccountBalance(ata);
|
|
117
|
+
return new BN(bal.value.amount);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (this.isMissingTokenAccountError(error)) {
|
|
120
|
+
return new BN(0);
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private isMissingTokenAccountError(error: unknown): boolean {
|
|
127
|
+
const message = String(
|
|
128
|
+
(error as { message?: string } | null)?.message ?? '',
|
|
129
|
+
).toLowerCase();
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
message.includes('failed to get token account balance') &&
|
|
133
|
+
message.includes('could not find account')
|
|
134
|
+
);
|
|
117
135
|
}
|
|
118
136
|
|
|
119
137
|
/**
|
|
@@ -37,7 +37,7 @@ const DEVNET_PROGRAM_IDS: SolanaProgramIds = {
|
|
|
37
37
|
LIQSOL_TOKEN: new PublicKey(devnetLiqSolTokenIDL.address),
|
|
38
38
|
VALIDATOR_LEADERBOARD: new PublicKey(devnetValidatorLeaderboardIDL.address),
|
|
39
39
|
TRANSFER_HOOK: new PublicKey(devnetTransferHookIDL.address),
|
|
40
|
-
ALT: new PublicKey("
|
|
40
|
+
ALT: new PublicKey("EG5pouZneDQxfw5coaWkkv5qeoJkRsgyMiq4r6bw7K2F")
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
export const PROGRAM_IDS_BY_CHAIN: Record<SupportedSolChainID, SolanaProgramIds> = {
|
|
@@ -323,6 +323,85 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
getLiqsolDestinationOwner(owner?: SolPubKey): SolPubKey {
|
|
327
|
+
return owner ?? this.squadsVaultPDA ?? this.anchor.wallet.publicKey;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
getLiqsolDestinationAta(owner?: SolPubKey): SolPubKey {
|
|
331
|
+
const destinationOwner = this.getLiqsolDestinationOwner(owner);
|
|
332
|
+
const mint = this.program.deriveLiqsolMintPda();
|
|
333
|
+
return getAssociatedTokenAddressSync(
|
|
334
|
+
mint,
|
|
335
|
+
destinationOwner,
|
|
336
|
+
true,
|
|
337
|
+
TOKEN_2022_PROGRAM_ID,
|
|
338
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async getLiqsolDestinationAtaState(owner?: SolPubKey): Promise<{
|
|
343
|
+
owner: SolPubKey;
|
|
344
|
+
mint: SolPubKey;
|
|
345
|
+
ata: SolPubKey;
|
|
346
|
+
exists: boolean;
|
|
347
|
+
}> {
|
|
348
|
+
const destinationOwner = this.getLiqsolDestinationOwner(owner);
|
|
349
|
+
const mint = this.program.deriveLiqsolMintPda();
|
|
350
|
+
const ata = this.getLiqsolDestinationAta(destinationOwner);
|
|
351
|
+
const accountInfo = await this.connection.getAccountInfo(ata, 'confirmed');
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
owner: destinationOwner,
|
|
355
|
+
mint,
|
|
356
|
+
ata,
|
|
357
|
+
exists: !!accountInfo,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async maybeBuildCreateLiqsolDestinationAtaIx(
|
|
362
|
+
owner?: SolPubKey,
|
|
363
|
+
): Promise<TransactionInstruction | null> {
|
|
364
|
+
const state = await this.getLiqsolDestinationAtaState(owner);
|
|
365
|
+
if (state.exists) {
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return createAssociatedTokenAccountInstruction(
|
|
370
|
+
this.feePayer,
|
|
371
|
+
state.ata,
|
|
372
|
+
state.owner,
|
|
373
|
+
state.mint,
|
|
374
|
+
TOKEN_2022_PROGRAM_ID,
|
|
375
|
+
ASSOCIATED_TOKEN_PROGRAM_ID,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async ensureLiqsolDestinationAta(owner?: SolPubKey): Promise<string | null> {
|
|
380
|
+
this.ensureUser();
|
|
381
|
+
const createAtaIx = await this.maybeBuildCreateLiqsolDestinationAtaIx(owner);
|
|
382
|
+
if (!createAtaIx) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
return await this.buildAndSendIx(createAtaIx);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (!this.isAtaAlreadyCreatedError(error)) {
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
return null;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private async prependLiqsolDestinationAtaIx(
|
|
397
|
+
ix: TransactionInstruction | TransactionInstruction[],
|
|
398
|
+
owner?: SolPubKey,
|
|
399
|
+
): Promise<TransactionInstruction[]> {
|
|
400
|
+
const ixs = Array.isArray(ix) ? [...ix] : [ix];
|
|
401
|
+
const createAtaIx = await this.maybeBuildCreateLiqsolDestinationAtaIx(owner);
|
|
402
|
+
return createAtaIx ? [createAtaIx, ...ixs] : ixs;
|
|
403
|
+
}
|
|
404
|
+
|
|
326
405
|
/**
|
|
327
406
|
* Claim accrued liqSOL distribution rewards (liqsol_core::claim_rewards).
|
|
328
407
|
*/
|
|
@@ -332,9 +411,10 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
332
411
|
|
|
333
412
|
try {
|
|
334
413
|
const ix = await this.distributionClient.buildClaimRewardsIx(owner);
|
|
414
|
+
const ixs = await this.prependLiqsolDestinationAtaIx(ix, owner);
|
|
335
415
|
return !!this.squadsX
|
|
336
|
-
? await this.sendSquadsIxs(
|
|
337
|
-
: await this.buildAndSendIx(
|
|
416
|
+
? await this.sendSquadsIxs(ixs)
|
|
417
|
+
: await this.buildAndSendIx(ixs);
|
|
338
418
|
} catch (err) {
|
|
339
419
|
console.log(`Failed to claim liqSOL rewards on Solana: ${err}`);
|
|
340
420
|
throw err;
|
|
@@ -374,10 +454,12 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
374
454
|
throw new Error('Unstake amount must be greater than zero.');
|
|
375
455
|
|
|
376
456
|
try {
|
|
457
|
+
const owner = this.squadsVaultPDA ?? this.anchor.wallet.publicKey;
|
|
377
458
|
const ix = await this.outpostClient.buildUnstakeIx(amountLamports, this.squadsVaultPDA)
|
|
459
|
+
const ixs = await this.prependLiqsolDestinationAtaIx(ix, owner);
|
|
378
460
|
return !!this.squadsX
|
|
379
|
-
? await this.sendSquadsIxs(
|
|
380
|
-
: await this.buildAndSendIx(
|
|
461
|
+
? await this.sendSquadsIxs(ixs)
|
|
462
|
+
: await this.buildAndSendIx(ixs)
|
|
381
463
|
}
|
|
382
464
|
catch (err) {
|
|
383
465
|
console.log(`Failed to unstake Solana: ${err}`);
|
|
@@ -462,10 +544,11 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
462
544
|
const user = this.squadsVaultPDA ?? this.anchor.wallet.publicKey;
|
|
463
545
|
const unstakeIx = await this.outpostClient.buildUnstakeIx(amountLamports, user);
|
|
464
546
|
const withdrawIx = await this.convertClient.buildWithdrawTx(amountLamports, user);
|
|
547
|
+
const ixs = await this.prependLiqsolDestinationAtaIx([unstakeIx, withdrawIx], user);
|
|
465
548
|
|
|
466
549
|
return !!this.squadsX
|
|
467
|
-
? await this.sendSquadsIxs(
|
|
468
|
-
: await this.buildAndSendIx(
|
|
550
|
+
? await this.sendSquadsIxs(ixs)
|
|
551
|
+
: await this.buildAndSendIx(ixs);
|
|
469
552
|
} catch (err) {
|
|
470
553
|
console.log(`Failed to unstake and withdraw: ${err}`);
|
|
471
554
|
throw err;
|
|
@@ -897,6 +980,13 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
897
980
|
|| normalized.includes('custom program error: 0x1784');
|
|
898
981
|
}
|
|
899
982
|
|
|
983
|
+
private isAtaAlreadyCreatedError(error: unknown): boolean {
|
|
984
|
+
const message = error instanceof Error ? error.message : String(error ?? '');
|
|
985
|
+
const normalized = message.toLowerCase();
|
|
986
|
+
return normalized.includes('already in use')
|
|
987
|
+
|| normalized.includes('already exists');
|
|
988
|
+
}
|
|
989
|
+
|
|
900
990
|
// ---------------------------------------------------------------------
|
|
901
991
|
// READ-ONLY Public Methods
|
|
902
992
|
// ---------------------------------------------------------------------
|
package/src/status.ts
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { Connection } from '@solana/web3.js';
|
|
2
|
+
import { BigNumber, ethers } from 'ethers';
|
|
3
|
+
|
|
4
|
+
import { EthereumStakingClient } from './networks/ethereum/ethereum';
|
|
5
|
+
import { formatContractErrors, resolveContractWriteOverrides } from './networks/ethereum/utils';
|
|
6
|
+
import { SolanaStakingClient } from './networks/solana/solana';
|
|
7
|
+
|
|
8
|
+
export type StakingTxChain = 'eth' | 'sol';
|
|
9
|
+
export type StakingTxState = 'pending' | 'confirmed' | 'failed' | 'not_found';
|
|
10
|
+
|
|
11
|
+
export interface StakingTransactionStatus {
|
|
12
|
+
chain: StakingTxChain;
|
|
13
|
+
txId: string;
|
|
14
|
+
state: StakingTxState;
|
|
15
|
+
timestampMs: number | null;
|
|
16
|
+
blockNumber?: number;
|
|
17
|
+
slot?: number;
|
|
18
|
+
confirmations?: number | null;
|
|
19
|
+
errorMessage?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EthereumSubmitStepResult {
|
|
23
|
+
stepId: 'deposit' | 'approve' | 'source';
|
|
24
|
+
label: string;
|
|
25
|
+
txId: string;
|
|
26
|
+
timestampMs: number | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface EthereumStakeSubmission {
|
|
30
|
+
finalTxId: string;
|
|
31
|
+
steps: EthereumSubmitStepResult[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type EthereumLikeProvider =
|
|
35
|
+
| ethers.providers.Web3Provider
|
|
36
|
+
| ethers.providers.JsonRpcProvider;
|
|
37
|
+
|
|
38
|
+
export async function getEthereumTransactionStatus(
|
|
39
|
+
source: EthereumStakingClient | EthereumLikeProvider,
|
|
40
|
+
txId: string,
|
|
41
|
+
): Promise<StakingTransactionStatus> {
|
|
42
|
+
const provider = isEthereumStakingClient(source)
|
|
43
|
+
? getEthereumProvider(source)
|
|
44
|
+
: source;
|
|
45
|
+
const tx = await provider.getTransaction(txId).catch(() => null);
|
|
46
|
+
const receipt = await provider.getTransactionReceipt(txId).catch(() => null);
|
|
47
|
+
|
|
48
|
+
if (!tx && !receipt) {
|
|
49
|
+
return {
|
|
50
|
+
chain: 'eth',
|
|
51
|
+
txId,
|
|
52
|
+
state: 'not_found',
|
|
53
|
+
timestampMs: null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!receipt) {
|
|
58
|
+
return {
|
|
59
|
+
chain: 'eth',
|
|
60
|
+
txId,
|
|
61
|
+
state: 'pending',
|
|
62
|
+
timestampMs: null,
|
|
63
|
+
confirmations: tx?.confirmations ?? null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const block = receipt.blockNumber != null
|
|
68
|
+
? await provider.getBlock(receipt.blockNumber).catch(() => null)
|
|
69
|
+
: null;
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
chain: 'eth',
|
|
73
|
+
txId,
|
|
74
|
+
state: receipt.status === 0 ? 'failed' : 'confirmed',
|
|
75
|
+
timestampMs: block?.timestamp ? block.timestamp * 1000 : null,
|
|
76
|
+
blockNumber: receipt.blockNumber,
|
|
77
|
+
confirmations: tx?.confirmations ?? null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function getSolanaTransactionStatus(
|
|
82
|
+
source: SolanaStakingClient | Connection,
|
|
83
|
+
txId: string,
|
|
84
|
+
): Promise<StakingTransactionStatus> {
|
|
85
|
+
const connection = isSolanaStakingClient(source)
|
|
86
|
+
? source.connection
|
|
87
|
+
: source;
|
|
88
|
+
|
|
89
|
+
const statusResponse = await connection.getSignatureStatuses(
|
|
90
|
+
[txId],
|
|
91
|
+
{ searchTransactionHistory: true },
|
|
92
|
+
).catch(() => ({ value: [null] }));
|
|
93
|
+
const status = statusResponse.value[0];
|
|
94
|
+
const transaction = await connection.getTransaction(txId, {
|
|
95
|
+
commitment: 'confirmed',
|
|
96
|
+
maxSupportedTransactionVersion: 0,
|
|
97
|
+
}).catch(() => null);
|
|
98
|
+
|
|
99
|
+
if (!status && !transaction) {
|
|
100
|
+
return {
|
|
101
|
+
chain: 'sol',
|
|
102
|
+
txId,
|
|
103
|
+
state: 'not_found',
|
|
104
|
+
timestampMs: null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const errorMessage = status?.err ? JSON.stringify(status.err) : undefined;
|
|
109
|
+
const isConfirmed = !!transaction || status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized';
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
chain: 'sol',
|
|
113
|
+
txId,
|
|
114
|
+
state: status?.err ? 'failed' : isConfirmed ? 'confirmed' : 'pending',
|
|
115
|
+
timestampMs: transaction?.blockTime ? transaction.blockTime * 1000 : null,
|
|
116
|
+
slot: transaction?.slot ?? status?.slot,
|
|
117
|
+
confirmations: status?.confirmations ?? null,
|
|
118
|
+
errorMessage,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function getStakingClientTransactionStatus(
|
|
123
|
+
client: EthereumStakingClient | SolanaStakingClient,
|
|
124
|
+
txId: string,
|
|
125
|
+
): Promise<StakingTransactionStatus> {
|
|
126
|
+
if (isEthereumStakingClient(client)) {
|
|
127
|
+
return getEthereumTransactionStatus(client, txId);
|
|
128
|
+
}
|
|
129
|
+
return getSolanaTransactionStatus(client, txId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export async function submitEthereumStakeToWireFlow(
|
|
133
|
+
client: EthereumStakingClient,
|
|
134
|
+
amount: bigint | string | number | BigNumber,
|
|
135
|
+
wireAccount: string,
|
|
136
|
+
): Promise<EthereumStakeSubmission> {
|
|
137
|
+
ensureEthereumUser(client);
|
|
138
|
+
|
|
139
|
+
const amountWei = BigNumber.isBigNumber(amount)
|
|
140
|
+
? amount
|
|
141
|
+
: BigNumber.from(amount);
|
|
142
|
+
const signerAddress = await client.address;
|
|
143
|
+
if (!signerAddress) {
|
|
144
|
+
throw new Error('Ethereum signer address is unavailable.');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const contractService = getEthereumContractService(client);
|
|
148
|
+
const depositor = client.contract.Depositor.address;
|
|
149
|
+
const liqRead = client.contract.LiqEthToken;
|
|
150
|
+
const liqWrite = contractService.getWrite('LiqEthToken');
|
|
151
|
+
const steps: EthereumSubmitStepResult[] = [];
|
|
152
|
+
|
|
153
|
+
const [balance, allowance, paused] = await Promise.all([
|
|
154
|
+
liqRead.balanceOf(signerAddress),
|
|
155
|
+
liqRead.allowance(signerAddress, depositor),
|
|
156
|
+
client.contract.Depositor.paused(),
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
if (paused) {
|
|
160
|
+
throw new Error('StakeClient.performStakeToWire: Depositor is in a paused state');
|
|
161
|
+
}
|
|
162
|
+
if (!wireAccount?.trim()) {
|
|
163
|
+
throw new Error('StakeClient.performStakeToWire: wireAccount is required');
|
|
164
|
+
}
|
|
165
|
+
if (balance.lt(amountWei)) {
|
|
166
|
+
throw new Error('StakeClient.performStakeToWire: Insufficient LiqETH balance');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (allowance.lt(amountWei)) {
|
|
170
|
+
const approveTx = await liqWrite.approve(depositor, amountWei);
|
|
171
|
+
const approveReceipt = await approveTx.wait(1);
|
|
172
|
+
const updatedAllowance = await liqRead.allowance(signerAddress, depositor);
|
|
173
|
+
if (updatedAllowance.lt(amountWei)) {
|
|
174
|
+
throw new Error('StakeClient.performStakeToWire: Liq allowance approval failed or allowance still insufficient after approve');
|
|
175
|
+
}
|
|
176
|
+
steps.push({
|
|
177
|
+
stepId: 'approve',
|
|
178
|
+
label: 'Approve LIQETH spend',
|
|
179
|
+
txId: approveTx.hash,
|
|
180
|
+
timestampMs: approveReceipt?.blockNumber != null
|
|
181
|
+
? await getEthereumBlockTimestampMs(getEthereumProvider(client), approveReceipt.blockNumber)
|
|
182
|
+
: null,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
await client.contract.Depositor.callStatic.stakeLiqETHToWire(amountWei, wireAccount);
|
|
188
|
+
} catch (error: any) {
|
|
189
|
+
const formatted = formatContractErrors(error);
|
|
190
|
+
throw new Error(`StakeClient.performStakeToWire: ${formatted.name ?? formatted.raw}`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const txOverrides = await resolveContractWriteOverrides(
|
|
194
|
+
client.contract.Depositor,
|
|
195
|
+
'stakeLiqETHToWire',
|
|
196
|
+
[amountWei, wireAccount],
|
|
197
|
+
);
|
|
198
|
+
const stakeTx = await client.contract.Depositor.stakeLiqETHToWire(
|
|
199
|
+
amountWei,
|
|
200
|
+
wireAccount,
|
|
201
|
+
txOverrides,
|
|
202
|
+
);
|
|
203
|
+
const stakeReceipt = await stakeTx.wait(1);
|
|
204
|
+
steps.push({
|
|
205
|
+
stepId: 'source',
|
|
206
|
+
label: 'Stake LIQETH to Wire',
|
|
207
|
+
txId: stakeTx.hash,
|
|
208
|
+
timestampMs: stakeReceipt?.blockNumber != null
|
|
209
|
+
? await getEthereumBlockTimestampMs(getEthereumProvider(client), stakeReceipt.blockNumber)
|
|
210
|
+
: null,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
finalTxId: stakeTx.hash,
|
|
215
|
+
steps,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function submitEthereumDepositAndStakeToWireFlow(
|
|
220
|
+
client: EthereumStakingClient,
|
|
221
|
+
amount: bigint | string | number | BigNumber,
|
|
222
|
+
wireAccount: string,
|
|
223
|
+
): Promise<EthereumStakeSubmission> {
|
|
224
|
+
ensureEthereumUser(client);
|
|
225
|
+
|
|
226
|
+
const amountWei = BigNumber.isBigNumber(amount)
|
|
227
|
+
? amount
|
|
228
|
+
: BigNumber.from(amount);
|
|
229
|
+
if (amountWei.lte(0)) {
|
|
230
|
+
throw new Error('Amount must be greater than zero.');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const convertClient = getEthereumConvertClient(client);
|
|
234
|
+
const depositResult = await convertClient.performDeposit(amountWei);
|
|
235
|
+
const steps: EthereumSubmitStepResult[] = [{
|
|
236
|
+
stepId: 'deposit',
|
|
237
|
+
label: 'Deposit native ETH',
|
|
238
|
+
txId: depositResult.txHash,
|
|
239
|
+
timestampMs: depositResult.receipt?.blockNumber != null
|
|
240
|
+
? await getEthereumBlockTimestampMs(getEthereumProvider(client), depositResult.receipt.blockNumber)
|
|
241
|
+
: null,
|
|
242
|
+
}];
|
|
243
|
+
|
|
244
|
+
const stake = await submitEthereumStakeToWireFlow(client, amountWei, wireAccount);
|
|
245
|
+
return {
|
|
246
|
+
finalTxId: stake.finalTxId,
|
|
247
|
+
steps: [...steps, ...stake.steps],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function isEthereumStakingClient(value: unknown): value is EthereumStakingClient {
|
|
252
|
+
return value instanceof EthereumStakingClient;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function isSolanaStakingClient(value: unknown): value is SolanaStakingClient {
|
|
256
|
+
return value instanceof SolanaStakingClient;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function getEthereumProvider(client: EthereumStakingClient): EthereumLikeProvider {
|
|
260
|
+
const provider = (client as unknown as { provider?: EthereumLikeProvider }).provider;
|
|
261
|
+
if (!provider) {
|
|
262
|
+
throw new Error('Ethereum provider is unavailable.');
|
|
263
|
+
}
|
|
264
|
+
return provider;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function getEthereumContractService(client: EthereumStakingClient): {
|
|
268
|
+
getWrite: (name: string) => any;
|
|
269
|
+
} {
|
|
270
|
+
const contractService = (client as unknown as { contractService?: { getWrite: (name: string) => any } }).contractService;
|
|
271
|
+
if (!contractService) {
|
|
272
|
+
throw new Error('Ethereum contract service is unavailable.');
|
|
273
|
+
}
|
|
274
|
+
return contractService;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function getEthereumConvertClient(client: EthereumStakingClient): {
|
|
278
|
+
performDeposit: (amountWei: BigNumber) => Promise<{
|
|
279
|
+
txHash: string;
|
|
280
|
+
receipt?: { blockNumber?: number };
|
|
281
|
+
}>;
|
|
282
|
+
} {
|
|
283
|
+
const convertClient = (client as unknown as { convertClient?: any }).convertClient;
|
|
284
|
+
if (!convertClient?.performDeposit) {
|
|
285
|
+
throw new Error('Ethereum convert client is unavailable.');
|
|
286
|
+
}
|
|
287
|
+
return convertClient;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function ensureEthereumUser(client: EthereumStakingClient): void {
|
|
291
|
+
const ensureUser = (client as unknown as { ensureUser?: () => void }).ensureUser;
|
|
292
|
+
ensureUser?.call(client);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function getEthereumBlockTimestampMs(
|
|
296
|
+
provider: EthereumLikeProvider,
|
|
297
|
+
blockNumber: number,
|
|
298
|
+
): Promise<number | null> {
|
|
299
|
+
const block = await provider.getBlock(blockNumber).catch(() => null);
|
|
300
|
+
return block?.timestamp ? block.timestamp * 1000 : null;
|
|
301
|
+
}
|