@wireio/stake 2.0.0 → 2.1.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/lib/stake.browser.js +100 -88
- package/lib/stake.browser.js.map +1 -1
- package/lib/stake.d.ts +45 -53
- package/lib/stake.js +100 -93
- package/lib/stake.js.map +1 -1
- package/lib/stake.m.js +100 -88
- package/lib/stake.m.js.map +1 -1
- package/package.json +1 -1
- package/src/assets/solana/devnet/idl/liqsol_core.json +15 -3
- package/src/assets/solana/devnet/types/liqsol_core.ts +15 -3
- package/src/assets/solana/mainnet/idl/liqsol_core.json +15 -3
- package/src/assets/solana/mainnet/types/liqsol_core.ts +15 -3
- package/src/networks/ethereum/ethereum.ts +0 -3
- package/src/networks/solana/clients/deposit.client.ts +119 -122
- package/src/networks/solana/clients/distribution.client.ts +2 -2
- package/src/networks/solana/clients/leaderboard.client.ts +2 -2
- package/src/networks/solana/clients/outpost.client.ts +3 -10
- package/src/networks/solana/clients/token.client.ts +2 -2
- package/src/networks/solana/program.ts +24 -9
- package/src/networks/solana/solana.ts +111 -32
|
@@ -29,6 +29,25 @@ import type { LiqsolCore as LiqSolCoreDevnet } from '../../assets/solana/devnet/
|
|
|
29
29
|
import type { LiqsolToken as LiqsolTokenDevnet } from '../../assets/solana/devnet/types/liqsol_token';
|
|
30
30
|
import type { ValidatorLeaderboard as ValidatorLeaderboardDevnet } from '../../assets/solana/devnet/types/validator_leaderboard';
|
|
31
31
|
|
|
32
|
+
// “Client” IDLs = mainnet OR devnet, plus `address` field we add at runtime
|
|
33
|
+
|
|
34
|
+
export type LiqsolCoreClientIdl =
|
|
35
|
+
(LiqSolCoreMainnet | LiqSolCoreDevnet) & { address: string };
|
|
36
|
+
|
|
37
|
+
export type LiqsolTokenClientIdl =
|
|
38
|
+
(LiqsolTokenMainnet | LiqsolTokenDevnet) & { address: string };
|
|
39
|
+
|
|
40
|
+
export type ValidatorLeaderboardClientIdl =
|
|
41
|
+
(ValidatorLeaderboardMainnet | ValidatorLeaderboardDevnet) & { address: string };
|
|
42
|
+
|
|
43
|
+
export type SolanaProgramIdlMap = {
|
|
44
|
+
liqsolCore: LiqsolCoreClientIdl;
|
|
45
|
+
liqsolToken: LiqsolTokenClientIdl;
|
|
46
|
+
validatorLeaderboard: ValidatorLeaderboardClientIdl;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type SolanaProgramName = keyof SolanaProgramIdlMap;
|
|
50
|
+
|
|
32
51
|
type Entry<IDL> = {
|
|
33
52
|
idl: IDL & { address: string };
|
|
34
53
|
address: string;
|
|
@@ -65,9 +84,6 @@ const PROGRAMS_BY_CHAIN = {
|
|
|
65
84
|
},
|
|
66
85
|
} as const;
|
|
67
86
|
|
|
68
|
-
export type SolanaProgramName =
|
|
69
|
-
keyof (typeof PROGRAMS_BY_CHAIN)[SolChainID.Mainnet];
|
|
70
|
-
|
|
71
87
|
export class SolanaProgramService {
|
|
72
88
|
private readonly ids: SolanaProgramIds;
|
|
73
89
|
private readonly programs: (typeof PROGRAMS_BY_CHAIN)[SolChainID.Mainnet];
|
|
@@ -90,15 +106,14 @@ export class SolanaProgramService {
|
|
|
90
106
|
return this.ids;
|
|
91
107
|
}
|
|
92
108
|
|
|
93
|
-
|
|
109
|
+
// Generic getProgram, typed by name → union IDL
|
|
94
110
|
getProgram<K extends SolanaProgramName>(
|
|
95
111
|
name: K,
|
|
96
|
-
): Program<
|
|
112
|
+
): Program<SolanaProgramIdlMap[K]> {
|
|
97
113
|
const { idl, address } = this.programs[name];
|
|
98
|
-
const idlWithAddr = { ...idl, address };
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
>;
|
|
114
|
+
const idlWithAddr = { ...(idl as any), address } as SolanaProgramIdlMap[K];
|
|
115
|
+
|
|
116
|
+
return new Program(idlWithAddr, this.provider) as Program<SolanaProgramIdlMap[K]>;
|
|
102
117
|
}
|
|
103
118
|
|
|
104
119
|
listProgramNames(): SolanaProgramName[] {
|
|
@@ -4,12 +4,14 @@ import {
|
|
|
4
4
|
Connection,
|
|
5
5
|
ConnectionConfig,
|
|
6
6
|
PerfSample,
|
|
7
|
+
SendTransactionError,
|
|
7
8
|
PublicKey as SolPubKey,
|
|
8
9
|
SystemProgram,
|
|
9
10
|
Transaction,
|
|
10
11
|
TransactionInstruction,
|
|
11
12
|
TransactionMessage,
|
|
12
13
|
TransactionSignature,
|
|
14
|
+
VersionedTransaction,
|
|
13
15
|
} from '@solana/web3.js';
|
|
14
16
|
import { AnchorProvider, BN } from '@coral-xyz/anchor';
|
|
15
17
|
import { BaseSignerWalletAdapter } from '@solana/wallet-adapter-base';
|
|
@@ -343,15 +345,16 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
343
345
|
// return sent2;
|
|
344
346
|
// }
|
|
345
347
|
// else {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
const ix = await this.depositClient.buildDepositTx(amountLamports)
|
|
349
|
+
const tx = new Transaction().add(ix);
|
|
350
|
+
const prepared = await this.prepareTx(tx);
|
|
351
|
+
const signed = await this.signTransaction(prepared.tx);
|
|
350
352
|
|
|
351
|
-
|
|
353
|
+
return this.sendAndConfirmHttp(signed, prepared);
|
|
352
354
|
// }
|
|
353
355
|
} catch (err) {
|
|
354
|
-
|
|
356
|
+
console.log(`Failed to deposit Solana: ${err}`);
|
|
357
|
+
throw err;
|
|
355
358
|
}
|
|
356
359
|
}
|
|
357
360
|
|
|
@@ -385,7 +388,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
385
388
|
|
|
386
389
|
return this.sendAndConfirmHttp(signed, prepared);
|
|
387
390
|
} catch (err) {
|
|
388
|
-
|
|
391
|
+
console.log(`Failed to withdraw Solana: ${err}`);
|
|
392
|
+
throw err;
|
|
389
393
|
}
|
|
390
394
|
}
|
|
391
395
|
|
|
@@ -415,7 +419,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
415
419
|
|
|
416
420
|
return this.sendAndConfirmHttp(signed, prepared);
|
|
417
421
|
} catch (err) {
|
|
418
|
-
|
|
422
|
+
console.log(`Failed to stake Solana: ${err}`);
|
|
423
|
+
throw err;
|
|
419
424
|
}
|
|
420
425
|
}
|
|
421
426
|
|
|
@@ -446,7 +451,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
446
451
|
return this.sendAndConfirmHttp(signed, prepared);
|
|
447
452
|
}
|
|
448
453
|
catch (err) {
|
|
449
|
-
|
|
454
|
+
console.log(`Failed to unstake Solana: ${err}`);
|
|
455
|
+
throw err;
|
|
450
456
|
}
|
|
451
457
|
}
|
|
452
458
|
|
|
@@ -474,7 +480,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
474
480
|
return this.sendAndConfirmHttp(signed, prepared);
|
|
475
481
|
}
|
|
476
482
|
catch (err) {
|
|
477
|
-
|
|
483
|
+
console.log(`Failed to buy liqSOL pretokens: ${err}`);
|
|
484
|
+
throw err;
|
|
478
485
|
}
|
|
479
486
|
}
|
|
480
487
|
|
|
@@ -627,7 +634,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
627
634
|
};
|
|
628
635
|
}
|
|
629
636
|
catch (err) {
|
|
630
|
-
|
|
637
|
+
console.log(`Failed to get Solana portfolio: ${err}`);
|
|
638
|
+
throw err;
|
|
631
639
|
}
|
|
632
640
|
}
|
|
633
641
|
|
|
@@ -689,7 +697,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
689
697
|
});
|
|
690
698
|
}
|
|
691
699
|
catch (err) {
|
|
692
|
-
|
|
700
|
+
console.log(`Failed to build Solana tranche snapshot: ${err}`);
|
|
701
|
+
throw err;
|
|
693
702
|
}
|
|
694
703
|
}
|
|
695
704
|
|
|
@@ -711,7 +720,8 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
711
720
|
|
|
712
721
|
return apyPercent;
|
|
713
722
|
} catch (err) {
|
|
714
|
-
|
|
723
|
+
console.log(`Failed to compute Solana system APY: ${err}`);
|
|
724
|
+
throw err;
|
|
715
725
|
}
|
|
716
726
|
}
|
|
717
727
|
|
|
@@ -1026,38 +1036,107 @@ export class SolanaStakingClient implements IStakingClient {
|
|
|
1026
1036
|
/**
|
|
1027
1037
|
* Send a signed transaction over HTTP RPC and wait for confirmation.
|
|
1028
1038
|
* Throws if the transaction fails.
|
|
1039
|
+
*
|
|
1040
|
+
* - Handles benign "already processed" preflight errors.
|
|
1041
|
+
* - Uses getSignatureStatuses() instead of confirmTransaction()
|
|
1042
|
+
* so we don't care about blockheight expiry.
|
|
1029
1043
|
*/
|
|
1030
1044
|
private async sendAndConfirmHttp(
|
|
1031
1045
|
signed: SolanaTransaction,
|
|
1032
|
-
|
|
1046
|
+
_ctx: { blockhash: string; lastValidBlockHeight: number },
|
|
1033
1047
|
): Promise<string> {
|
|
1034
1048
|
this.ensureUser();
|
|
1035
1049
|
|
|
1036
|
-
const
|
|
1037
|
-
|
|
1038
|
-
|
|
1050
|
+
const rawTx = signed.serialize();
|
|
1051
|
+
let signature: string;
|
|
1052
|
+
|
|
1053
|
+
// -------------------------------------------------------------
|
|
1054
|
+
// 1) Send the raw transaction
|
|
1055
|
+
// Handle "already been processed" as a soft success.
|
|
1056
|
+
// -------------------------------------------------------------
|
|
1057
|
+
try {
|
|
1058
|
+
signature = await this.connection.sendRawTransaction(rawTx, {
|
|
1039
1059
|
skipPreflight: false,
|
|
1040
1060
|
preflightCommitment: commitment,
|
|
1041
1061
|
maxRetries: 3,
|
|
1042
|
-
}
|
|
1043
|
-
)
|
|
1062
|
+
});
|
|
1063
|
+
} catch (e: any) {
|
|
1064
|
+
const msg = e?.message ?? '';
|
|
1065
|
+
|
|
1066
|
+
const isSendTxError =
|
|
1067
|
+
e instanceof SendTransactionError || e?.name === 'SendTransactionError';
|
|
1068
|
+
|
|
1069
|
+
if (isSendTxError && msg.includes('already been processed')) {
|
|
1070
|
+
// Preflight is telling us this exact txid already landed.
|
|
1071
|
+
// Derive the signature from the signed tx and continue to confirmation.
|
|
1072
|
+
console.warn(
|
|
1073
|
+
'sendRawTransaction preflight says "already been processed"; ' +
|
|
1074
|
+
'treating as success and deriving signature from signed tx.',
|
|
1075
|
+
);
|
|
1044
1076
|
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
signature
|
|
1048
|
-
blockhash: ctx.blockhash,
|
|
1049
|
-
lastValidBlockHeight: ctx.lastValidBlockHeight,
|
|
1050
|
-
},
|
|
1051
|
-
commitment,
|
|
1052
|
-
);
|
|
1077
|
+
// Assuming SolanaTransaction is a legacy Transaction here.
|
|
1078
|
+
const legacy = signed as unknown as Transaction;
|
|
1079
|
+
const first = legacy.signatures?.[0]?.signature;
|
|
1053
1080
|
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1081
|
+
if (!first) {
|
|
1082
|
+
// No way to recover the txid → rethrow
|
|
1083
|
+
throw e;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
signature = bs58.encode(first);
|
|
1087
|
+
} else {
|
|
1088
|
+
// Any other send error is a real failure
|
|
1089
|
+
throw e;
|
|
1090
|
+
}
|
|
1058
1091
|
}
|
|
1059
1092
|
|
|
1060
|
-
|
|
1093
|
+
// -------------------------------------------------------------
|
|
1094
|
+
// 2) Poll getSignatureStatuses until confirmed/finalized or timeout
|
|
1095
|
+
// -------------------------------------------------------------
|
|
1096
|
+
const start = Date.now();
|
|
1097
|
+
const timeoutMs = 30_000; // tune this if you want longer for devnet
|
|
1098
|
+
const pollIntervalMs = 500; // 0.5s between polls
|
|
1099
|
+
|
|
1100
|
+
// eslint-disable-next-line no-constant-condition
|
|
1101
|
+
while (true) {
|
|
1102
|
+
const { value } = await this.connection.getSignatureStatuses(
|
|
1103
|
+
[signature],
|
|
1104
|
+
{ searchTransactionHistory: true },
|
|
1105
|
+
);
|
|
1106
|
+
const status = value[0];
|
|
1107
|
+
|
|
1108
|
+
if (status) {
|
|
1109
|
+
if (status.err) {
|
|
1110
|
+
// On-chain error (instruction error, etc.)
|
|
1111
|
+
throw new Error(
|
|
1112
|
+
`Transaction failed: ${JSON.stringify(status.err)}`,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
const confirmations = status.confirmations;
|
|
1117
|
+
const confirmationStatus = status.confirmationStatus;
|
|
1118
|
+
|
|
1119
|
+
const satisfied =
|
|
1120
|
+
confirmationStatus === 'confirmed' ||
|
|
1121
|
+
confirmationStatus === 'finalized' ||
|
|
1122
|
+
confirmations === null || // rooted
|
|
1123
|
+
(confirmations ?? 0) > 0; // at least 1 confirmation
|
|
1124
|
+
|
|
1125
|
+
if (satisfied) {
|
|
1126
|
+
// We consider this successfully confirmed
|
|
1127
|
+
return signature;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// Timeout guard so we don't spin forever on a tx that never lands
|
|
1132
|
+
if (Date.now() - start > timeoutMs) {
|
|
1133
|
+
throw new Error(
|
|
1134
|
+
`Transaction confirmation timed out for ${signature}`,
|
|
1135
|
+
);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
1139
|
+
}
|
|
1061
1140
|
}
|
|
1062
1141
|
|
|
1063
1142
|
/**
|