@ledgerhq/coin-tester-solana 1.1.0 → 1.1.1-nightly.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ledgerhq/coin-tester-solana",
3
- "version": "1.1.0",
3
+ "version": "1.1.1-nightly.1",
4
4
  "description": "Ledger Solana Coin Tester",
5
5
  "main": "src/scenarii.test.ts",
6
6
  "keywords": [
@@ -49,14 +49,14 @@
49
49
  "bignumber.js": "^9",
50
50
  "docker-compose": "^1",
51
51
  "msw": "^2",
52
- "@ledgerhq/coin-framework": "^5.2.0",
52
+ "@ledgerhq/coin-framework": "^5.3.0-nightly.1",
53
53
  "@ledgerhq/coin-tester": "^0.6.0",
54
- "@ledgerhq/coin-solana": "^0.25.0",
55
- "@ledgerhq/cryptoassets": "^13.18.0",
56
- "@ledgerhq/live-env": "^2.9.0",
57
- "@ledgerhq/live-signer-solana": "^0.2.0",
54
+ "@ledgerhq/coin-solana": "^0.25.1-nightly.1",
55
+ "@ledgerhq/cryptoassets": "^13.18.1-nightly.0",
56
+ "@ledgerhq/live-signer-solana": "^0.2.1-nightly.1",
58
57
  "@ledgerhq/types-cryptoassets": "^7.23.0",
59
- "@ledgerhq/types-live": "^6.72.0"
58
+ "@ledgerhq/types-live": "^6.73.0-nightly.1",
59
+ "@ledgerhq/live-env": "^2.9.1-nightly.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/jest": "^29",
package/src/connection.ts CHANGED
@@ -1,4 +1,11 @@
1
- import { Connection, Keypair, PublicKey } from "@solana/web3.js";
1
+ import {
2
+ Authorized,
3
+ Connection,
4
+ Keypair,
5
+ PublicKey,
6
+ StakeProgram,
7
+ VoteAccountInfo,
8
+ } from "@solana/web3.js";
2
9
  import { createAssociatedTokenAccountIdempotent, mintTo } from "@solana/spl-token";
3
10
  import { TokenCurrency } from "@ledgerhq/types-cryptoassets";
4
11
  import { SolanaTokenProgram } from "@ledgerhq/coin-solana/types";
@@ -16,6 +23,8 @@ export const PAYER = Keypair.fromSecretKey(
16
23
  207,
17
24
  ]),
18
25
  );
26
+ export const STAKE_ACCOUNT = Keypair.generate();
27
+ export let VOTE_ACCOUNT: VoteAccountInfo | null = null;
19
28
 
20
29
  export async function createSplAccount(
21
30
  address: string,
@@ -46,3 +55,25 @@ export async function createSplAccount(
46
55
  );
47
56
  await connection.confirmTransaction({ signature, ...latest });
48
57
  }
58
+
59
+ export async function initVoteAccount() {
60
+ const voteAccounts = await connection.getVoteAccounts();
61
+ VOTE_ACCOUNT = voteAccounts.current[0];
62
+ }
63
+
64
+ export async function initStakeAccount(address: string, amount: number) {
65
+ const authority = new PublicKey(address);
66
+ const transaction = StakeProgram.createAccount({
67
+ fromPubkey: PAYER.publicKey,
68
+ stakePubkey: STAKE_ACCOUNT.publicKey,
69
+ authorized: new Authorized(authority, authority),
70
+ lamports: amount,
71
+ });
72
+ const latest = await connection.getLatestBlockhash();
73
+ transaction.feePayer = PAYER.publicKey;
74
+ transaction.recentBlockhash = latest.blockhash;
75
+ transaction.lastValidBlockHeight = latest.lastValidBlockHeight;
76
+ transaction.sign(PAYER, STAKE_ACCOUNT);
77
+ const signature = await connection.sendRawTransaction(transaction.serialize());
78
+ await connection.confirmTransaction({ signature, ...latest });
79
+ }
package/src/fixtures.ts CHANGED
@@ -13,6 +13,7 @@ export const RECIPIENT = "Hj69wRzkrFuf1Nby4yzPEFHdsmQdMoVYjvDKZSLjZFEp";
13
13
  export const SOLANA = getCryptoCurrencyById("solana");
14
14
  export const SOLANA_USDC = getTokenById("solana/spl/epjfwdd5aufqssqem2qn1xzybapc8g4weggkzwytdt1v");
15
15
  export const SOLANA_CWIF = getTokenById("solana/spl/7atgf8kqo4wjrd5atgx7t1v2zvvykpjbffnevf1icfv1");
16
+ export const WITHDRAWABLE_AMOUNT = 2e9;
16
17
 
17
18
  export const makeAccount = (
18
19
  address: string,
@@ -3,7 +3,15 @@ import { SolanaAccount, Transaction as SolanaTransaction } from "@ledgerhq/coin-
3
3
  import { killSpeculos, spawnSpeculos } from "@ledgerhq/coin-tester/signers/speculos";
4
4
  import resolver from "@ledgerhq/coin-solana/hw-getAddress";
5
5
  import { LegacySignerSolana } from "@ledgerhq/live-signer-solana";
6
- import { RECIPIENT, SOLANA, SOLANA_CWIF, SOLANA_USDC, initMSW, makeAccount } from "../fixtures";
6
+ import {
7
+ RECIPIENT,
8
+ SOLANA,
9
+ SOLANA_CWIF,
10
+ SOLANA_USDC,
11
+ WITHDRAWABLE_AMOUNT,
12
+ initMSW,
13
+ makeAccount,
14
+ } from "../fixtures";
7
15
  import { CoinConfig } from "@ledgerhq/coin-framework/config";
8
16
  import solanaCoinConfig, { SolanaCoinConfig } from "@ledgerhq/coin-solana/config";
9
17
  import BigNumber from "bignumber.js";
@@ -12,7 +20,14 @@ import { airdrop, killAgave, spawnAgave } from "../agave";
12
20
  import { encodeAccountIdWithTokenAccountAddress } from "@ledgerhq/coin-solana/logic";
13
21
  import { TOKEN_2022_PROGRAM_ID, getAssociatedTokenAddressSync } from "@solana/spl-token";
14
22
  import { PublicKey } from "@solana/web3.js";
15
- import { PAYER, createSplAccount } from "../connection";
23
+ import {
24
+ PAYER,
25
+ STAKE_ACCOUNT,
26
+ VOTE_ACCOUNT,
27
+ createSplAccount,
28
+ initStakeAccount,
29
+ initVoteAccount,
30
+ } from "../connection";
16
31
  import { Config, getChainAPI } from "@ledgerhq/coin-solana/network/index";
17
32
  import { makeBridges } from "@ledgerhq/coin-solana/bridge/bridge";
18
33
 
@@ -26,6 +41,14 @@ type SolanaScenarioTransaction = ScenarioTransaction<SolanaTransaction, SolanaAc
26
41
  export const defaultNanoApp = { firmware: "2.4.2", version: "1.9.1" } as const;
27
42
 
28
43
  function makeScenarioTransactions(address: string): SolanaScenarioTransaction[] {
44
+ if (!VOTE_ACCOUNT) {
45
+ throw new Error("Vote account not initialized");
46
+ }
47
+
48
+ if (!STAKE_ACCOUNT) {
49
+ throw new Error("Stake account not initialized");
50
+ }
51
+
29
52
  const scenarioSendSolTransaction: SolanaScenarioTransaction = {
30
53
  name: "Send 1 Sol",
31
54
  amount: new BigNumber(1e9),
@@ -166,6 +189,76 @@ function makeScenarioTransactions(address: string): SolanaScenarioTransaction[]
166
189
  },
167
190
  };
168
191
 
192
+ const scenarioCreateSolStakeAccountTransaction: SolanaScenarioTransaction = {
193
+ name: "Create Stake Account 1 Sol",
194
+ amount: new BigNumber(1e9),
195
+ model: {
196
+ kind: "stake.createAccount",
197
+ uiState: { delegate: { voteAccAddress: VOTE_ACCOUNT.votePubkey } },
198
+ },
199
+ expect: (previousAccount, currentAccount) => {
200
+ const [latestOperation] = currentAccount.operations;
201
+ expect(currentAccount.operations.length - previousAccount.operations.length).toEqual(1);
202
+ expect(latestOperation.type).toEqual("DELEGATE");
203
+ expect(latestOperation.value).toStrictEqual(latestOperation.fee);
204
+ expect(latestOperation.senders).toStrictEqual([]);
205
+ expect(latestOperation.recipients).toStrictEqual([]);
206
+ expect(latestOperation.extra).toStrictEqual({
207
+ stake: { address: VOTE_ACCOUNT?.votePubkey, amount: new BigNumber(1e9 + 2287880) }, // amount + rent exempt reserve + fee
208
+ });
209
+ expect(currentAccount.balance).toStrictEqual(
210
+ previousAccount.balance.minus(latestOperation.value),
211
+ );
212
+ expect(currentAccount.spendableBalance).toStrictEqual(
213
+ previousAccount.spendableBalance.minus(1e9 + 2297880),
214
+ );
215
+ },
216
+ };
217
+
218
+ const scenarioActivateStakeAccount: SolanaScenarioTransaction = {
219
+ name: "Activate Stake Account",
220
+ model: {
221
+ kind: "stake.delegate",
222
+ uiState: {
223
+ stakeAccAddr: STAKE_ACCOUNT.publicKey.toBase58(),
224
+ voteAccAddr: VOTE_ACCOUNT.votePubkey,
225
+ },
226
+ },
227
+ expect: (previousAccount, currentAccount) => {
228
+ const [latestOperation] = currentAccount.operations;
229
+ expect(currentAccount.operations.length - previousAccount.operations.length).toEqual(1);
230
+ expect(latestOperation.type).toEqual("DELEGATE");
231
+ expect(latestOperation.value).toStrictEqual(latestOperation.fee);
232
+ expect(latestOperation.senders).toStrictEqual([]);
233
+ expect(latestOperation.recipients).toStrictEqual([]);
234
+ expect(latestOperation.extra).toStrictEqual({
235
+ stake: { address: VOTE_ACCOUNT?.votePubkey, amount: latestOperation.value },
236
+ });
237
+ expect(currentAccount.balance).toStrictEqual(
238
+ previousAccount.balance.minus(latestOperation.value),
239
+ );
240
+ },
241
+ };
242
+
243
+ const scenarioDeactivateStakeAccount: SolanaScenarioTransaction = {
244
+ name: "Deactivate Stake Account",
245
+ model: {
246
+ kind: "stake.undelegate",
247
+ uiState: { stakeAccAddr: STAKE_ACCOUNT.publicKey.toBase58() },
248
+ },
249
+ expect: (previousAccount, currentAccount) => {
250
+ const [latestOperation] = currentAccount.operations;
251
+ expect(currentAccount.operations.length - previousAccount.operations.length).toEqual(1);
252
+ expect(latestOperation.type).toEqual("UNDELEGATE");
253
+ expect(latestOperation.value).toStrictEqual(latestOperation.fee);
254
+ expect(latestOperation.senders).toStrictEqual([]);
255
+ expect(latestOperation.recipients).toStrictEqual([]);
256
+ expect(currentAccount.balance).toStrictEqual(
257
+ previousAccount.balance.minus(latestOperation.value),
258
+ );
259
+ },
260
+ };
261
+
169
262
  const scenarioSendAllSolTransaction: SolanaScenarioTransaction = {
170
263
  name: "Send All Sol",
171
264
  useAllAmount: true,
@@ -182,13 +275,73 @@ function makeScenarioTransactions(address: string): SolanaScenarioTransaction[]
182
275
  expect(currentAccount.spendableBalance).toStrictEqual(new BigNumber(0));
183
276
  },
184
277
  };
278
+
279
+ const scenarioStakeWithdrawTransaction: SolanaScenarioTransaction = {
280
+ name: "Withdraw From Stake Account",
281
+ model: {
282
+ kind: "stake.withdraw",
283
+ uiState: {
284
+ stakeAccAddr: STAKE_ACCOUNT.publicKey.toBase58(),
285
+ },
286
+ },
287
+ expect: (previousAccount, currentAccount) => {
288
+ const [latestOperation] = currentAccount.operations;
289
+ expect(currentAccount.operations.length - previousAccount.operations.length).toEqual(1);
290
+ expect(latestOperation.type).toEqual("WITHDRAW_UNBONDED");
291
+ expect(latestOperation.value).toStrictEqual(latestOperation.fee);
292
+ expect(latestOperation.senders).toStrictEqual([]);
293
+ expect(latestOperation.recipients).toStrictEqual([]);
294
+ expect(latestOperation.extra).toStrictEqual({
295
+ stake: {
296
+ address: STAKE_ACCOUNT?.publicKey.toBase58(),
297
+ amount: new BigNumber(WITHDRAWABLE_AMOUNT),
298
+ },
299
+ });
300
+ expect(currentAccount.balance).toStrictEqual(
301
+ previousAccount.balance.minus(latestOperation.value),
302
+ );
303
+ expect(currentAccount.spendableBalance).toStrictEqual(
304
+ previousAccount.spendableBalance.plus(WITHDRAWABLE_AMOUNT),
305
+ );
306
+ },
307
+ };
308
+
309
+ const scenarioCreateAllSolStakeAccountTransaction: SolanaScenarioTransaction = {
310
+ name: "Create Stake Account All Sol",
311
+ useAllAmount: true,
312
+ model: {
313
+ kind: "stake.createAccount",
314
+ uiState: { delegate: { voteAccAddress: VOTE_ACCOUNT.votePubkey } },
315
+ },
316
+ expect: (previousAccount, currentAccount) => {
317
+ const [latestOperation] = currentAccount.operations;
318
+ expect(currentAccount.operations.length - previousAccount.operations.length).toEqual(1);
319
+ expect(latestOperation.type).toEqual("DELEGATE");
320
+ expect(latestOperation.value).toStrictEqual(latestOperation.fee);
321
+ expect(latestOperation.senders).toStrictEqual([]);
322
+ expect(latestOperation.recipients).toStrictEqual([]);
323
+ expect(latestOperation.extra).toMatchObject({
324
+ stake: { address: VOTE_ACCOUNT?.votePubkey },
325
+ });
326
+ expect(currentAccount.balance).toStrictEqual(
327
+ previousAccount.balance.minus(latestOperation.value),
328
+ );
329
+ expect(currentAccount.spendableBalance).toStrictEqual(new BigNumber(0));
330
+ },
331
+ };
332
+
185
333
  return [
186
334
  scenarioSendSolTransaction,
187
335
  scenarioSendUsdcTransaction,
188
336
  scenarioSendAllUsdcTransaction,
189
337
  scenarioSendCwifTransaction,
190
338
  scenarioSendAllCwifTransaction,
339
+ scenarioCreateSolStakeAccountTransaction,
340
+ scenarioActivateStakeAccount,
341
+ scenarioDeactivateStakeAccount,
191
342
  scenarioSendAllSolTransaction,
343
+ scenarioStakeWithdrawTransaction,
344
+ scenarioCreateAllSolStakeAccountTransaction,
192
345
  ];
193
346
  }
194
347
 
@@ -235,6 +388,8 @@ export const scenarioSolana: Scenario<SolanaTransaction, SolanaAccount> = {
235
388
  await airdrop(PAYER.publicKey.toBase58(), 5);
236
389
  await createSplAccount(account.freshAddress, SOLANA_USDC, 5, "spl-token");
237
390
  await createSplAccount(account.freshAddress, SOLANA_CWIF, 5, "spl-token-2022");
391
+ await initVoteAccount();
392
+ await initStakeAccount(account.freshAddress, WITHDRAWABLE_AMOUNT);
238
393
 
239
394
  initMSW();
240
395