@cardano-sdk/e2e 0.15.0 → 0.16.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.
@@ -12,6 +12,10 @@ services:
12
12
  <<: *logging
13
13
  build:
14
14
  context: ./local-network
15
+ depends_on:
16
+ # We need the file server here in order to calculate the pool metadata hashes
17
+ file-server:
18
+ condition: service_healthy
15
19
  environment:
16
20
  CARDANO_NODE_LOG_LEVEL: ${CARDANO_NODE_LOG_LEVEL:-Info}
17
21
  CARDANO_NODE_CHAINDB_LOG_LEVEL: ${CARDANO_NODE_CHAINDB_LOG_LEVEL:-Notice}
@@ -20,10 +24,7 @@ services:
20
24
  volumes:
21
25
  - ./local-network/network-files/node-sp1/:/root/network-files/node-sp1
22
26
  - ./local-network/config:/root/config
23
- depends_on:
24
- # We need the file server here in order to calculate the pool metadata hashes
25
- file-server:
26
- condition: service_healthy
27
+ - sdk-ipc:/sdk-ipc
27
28
 
28
29
  file-server:
29
30
  <<: *logging
@@ -39,12 +40,15 @@ services:
39
40
  timeout: 10s
40
41
 
41
42
  cardano-node-ogmios:
43
+ entrypoint: [ "/tini", "-g", "--", "/scripts/cardano-node-ogmios.sh" ]
42
44
  image: cardanosolutions/cardano-node-ogmios:v${OGMIOS_VERSION:-5.6.0}_${CARDANO_NODE_VERSION:-1.35.5}
43
45
  depends_on:
44
46
  local-testnet:
45
47
  condition: service_healthy
46
48
  volumes:
47
49
  - ./local-network/config/network:/config
50
+ - ./local-network/scripts:/scripts
51
+ - sdk-ipc:/sdk-ipc
48
52
 
49
53
  cardano-db-sync:
50
54
  depends_on:
@@ -72,6 +76,12 @@ services:
72
76
  condition: service_healthy
73
77
  restart: on-failure
74
78
 
79
+ handle-projector:
80
+ environment:
81
+ HANDLE_POLICY_IDS_FILE: /sdk-ipc/handle_policy_ids
82
+ volumes:
83
+ - sdk-ipc:/sdk-ipc
84
+
75
85
  provider-server:
76
86
  environment:
77
87
  TOKEN_METADATA_SERVER_URL: stub://
@@ -79,4 +89,10 @@ services:
79
89
  - ./local-network/config/network:/config
80
90
 
81
91
  volumes:
92
+ sdk-ipc:
93
+ driver: local
94
+ driver_opts:
95
+ device: ./local-network/sdk-ipc
96
+ o: bind
97
+ type: none
82
98
  wallet-db:
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ # Simple scripts which overrides the original cardano-node-ogmios.sh file from the
4
+ # cardano-node-ogmios docker image.
5
+
6
+ # Used to support the e2e test to check the projector is able to
7
+ # connect / reconnect to the ogmios server.
8
+
9
+ # If the test set the file, wait for its removal before starting the container
10
+ while [ -f /sdk-ipc/prevent_ogmios ] ; do sleep 10 ; done
11
+
12
+ # Start the cardano-node-ogmios as normal
13
+ /root/cardano-node-ogmios.sh
@@ -25,6 +25,7 @@ cat >network-files/utxo-keys/handles-metadata.json <<EOL
25
25
  }}
26
26
  EOL
27
27
 
28
+ echo $policyid > /sdk-ipc/handle_policy_ids
28
29
 
29
30
  addr=$(cardano-cli address build --payment-verification-key-file network-files/utxo-keys/utxo1.vkey --testnet-magic 888)
30
31
  faucetAddr="addr_test1qqen0wpmhg7fhkus45lyv4wju26cecgu6avplrnm6dgvuk6qel5hu3u3q0fht53ly97yx95hkt56j37ch07pesf6s4pqh5gd4e"
@@ -0,0 +1,5 @@
1
+ ## Description
2
+
3
+ Empty directory used as IPC between SDK containers and with the extern.
4
+
5
+ Used for files required by tests or to share ADA handle policy id between containers, etc...
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardano-sdk/e2e",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "End to end tests for the cardano-js-sdk packages.",
5
5
  "engines": {
6
6
  "node": ">=16.0"
@@ -81,18 +81,18 @@
81
81
  "dependencies": {
82
82
  "@cardano-foundation/ledgerjs-hw-app-cardano": "^6.0.0",
83
83
  "@cardano-ogmios/client": "5.6.0",
84
- "@cardano-sdk/cardano-services": "~0.13.3",
85
- "@cardano-sdk/cardano-services-client": "~0.9.9",
86
- "@cardano-sdk/core": "~0.15.0",
87
- "@cardano-sdk/crypto": "~0.1.6",
88
- "@cardano-sdk/hardware-ledger": "~0.2.8",
89
- "@cardano-sdk/key-management": "~0.7.7",
90
- "@cardano-sdk/ogmios": "~0.12.5",
91
- "@cardano-sdk/tx-construction": "~0.8.0",
92
- "@cardano-sdk/util": "~0.11.0",
93
- "@cardano-sdk/util-dev": "~0.13.3",
94
- "@cardano-sdk/util-rxjs": "~0.4.16",
95
- "@cardano-sdk/wallet": "~0.16.3",
84
+ "@cardano-sdk/cardano-services": "~0.14.0",
85
+ "@cardano-sdk/cardano-services-client": "~0.10.0",
86
+ "@cardano-sdk/core": "~0.15.1",
87
+ "@cardano-sdk/crypto": "~0.1.7",
88
+ "@cardano-sdk/hardware-ledger": "~0.2.9",
89
+ "@cardano-sdk/key-management": "~0.7.8",
90
+ "@cardano-sdk/ogmios": "~0.12.6",
91
+ "@cardano-sdk/tx-construction": "~0.8.1",
92
+ "@cardano-sdk/util": "~0.12.0",
93
+ "@cardano-sdk/util-dev": "~0.13.4",
94
+ "@cardano-sdk/util-rxjs": "~0.5.0",
95
+ "@cardano-sdk/wallet": "~0.17.0",
96
96
  "@vespaiach/axios-fetch-adapter": "^0.3.0",
97
97
  "axios": "^0.27.2",
98
98
  "bunyan": "^1.8.15",
@@ -122,16 +122,17 @@
122
122
  "@babel/core": "^7.18.2",
123
123
  "@babel/preset-env": "^7.18.2",
124
124
  "@babel/preset-typescript": "^7.17.12",
125
- "@cardano-sdk/dapp-connector": "~0.9.7",
126
- "@cardano-sdk/projection": "~0.6.9",
127
- "@cardano-sdk/projection-typeorm": "~0.3.5",
128
- "@cardano-sdk/web-extension": "~0.13.0",
125
+ "@cardano-sdk/dapp-connector": "~0.9.8",
126
+ "@cardano-sdk/projection": "~0.6.10",
127
+ "@cardano-sdk/projection-typeorm": "~0.3.6",
128
+ "@cardano-sdk/web-extension": "~0.13.1",
129
129
  "@dcspark/cardano-multiplatform-lib-browser": "^3.1.1",
130
130
  "@emurgo/cardano-message-signing-asmjs": "^1.0.1",
131
131
  "@types/bunyan": "^1.8.8",
132
132
  "@types/chalk": "^2.2.0",
133
133
  "@types/convict": "^6.1.2",
134
134
  "@types/delay": "^3.1.0",
135
+ "@types/dockerode": "^3.3.8",
135
136
  "@types/jest": "^28.1.2",
136
137
  "@types/lodash": "^4.14.182",
137
138
  "@types/ora": "^3.2.0",
@@ -146,7 +147,7 @@
146
147
  "babel-loader": "^8.2.5",
147
148
  "blake2b-no-wasm": "2.1.4",
148
149
  "buffer": "^6.0.3",
149
- "chromedriver": "^112.0.0",
150
+ "chromedriver": "^114.0.0",
150
151
  "copy-webpack-plugin": "^10.2.4",
151
152
  "eslint": "^7.32.0",
152
153
  "events": "^3.3.0",
@@ -173,5 +174,5 @@
173
174
  "webpack-cli": "^4.9.2",
174
175
  "webpack-merge": "^5.8.0"
175
176
  },
176
- "gitHead": "7368568d3dd7844bb48513c92f3a7f26773bf2c7"
177
+ "gitHead": "46029ae65c25ce6cfef394249c7ff5a32259757f"
177
178
  }
package/src/factories.ts CHANGED
@@ -42,8 +42,7 @@ import {
42
42
  rewardsHttpProvider,
43
43
  stakePoolHttpProvider,
44
44
  txSubmitHttpProvider,
45
- utxoHttpProvider,
46
- version
45
+ utxoHttpProvider
47
46
  } from '@cardano-sdk/cardano-services-client';
48
47
  import { LedgerKeyAgent } from '@cardano-sdk/hardware-ledger';
49
48
  import { Logger } from 'ts-log';
@@ -105,8 +104,7 @@ assetProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logger)
105
104
  assetInfoHttpProvider({
106
105
  adapter: customHttpFetchAdapter,
107
106
  baseUrl: params.baseUrl,
108
- logger,
109
- version
107
+ logger
110
108
  })
111
109
  );
112
110
  });
@@ -118,7 +116,7 @@ chainHistoryProviderFactory.register(
118
116
  if (params.baseUrl === undefined) throw new Error(`${chainHistoryHttpProvider.name}: ${MISSING_URL_PARAM}`);
119
117
 
120
118
  return new Promise<ChainHistoryProvider>(async (resolve) => {
121
- resolve(chainHistoryHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger, version }));
119
+ resolve(chainHistoryHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger }));
122
120
  });
123
121
  }
124
122
  );
@@ -129,7 +127,7 @@ networkInfoProviderFactory.register(
129
127
  if (params.baseUrl === undefined) throw new Error(`${networkInfoHttpProvider.name}: ${MISSING_URL_PARAM}`);
130
128
 
131
129
  return new Promise<NetworkInfoProvider>(async (resolve) => {
132
- resolve(networkInfoHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger, version }));
130
+ resolve(networkInfoHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger }));
133
131
  });
134
132
  }
135
133
  );
@@ -138,7 +136,7 @@ rewardsProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logge
138
136
  if (params.baseUrl === undefined) throw new Error(`${rewardsHttpProvider.name}: ${MISSING_URL_PARAM}`);
139
137
 
140
138
  return new Promise<RewardsProvider>(async (resolve) => {
141
- resolve(rewardsHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger, version }));
139
+ resolve(rewardsHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger }));
142
140
  });
143
141
  });
144
142
 
@@ -160,7 +158,7 @@ txSubmitProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logg
160
158
  if (params.baseUrl === undefined) throw new Error(`${txSubmitHttpProvider.name}: ${MISSING_URL_PARAM}`);
161
159
 
162
160
  return new Promise<TxSubmitProvider>(async (resolve) => {
163
- resolve(txSubmitHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger, version }));
161
+ resolve(txSubmitHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger }));
164
162
  });
165
163
  });
166
164
 
@@ -168,7 +166,7 @@ utxoProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logger):
168
166
  if (params.baseUrl === undefined) throw new Error(`${utxoHttpProvider.name}: ${MISSING_URL_PARAM}`);
169
167
 
170
168
  return new Promise<UtxoProvider>(async (resolve) => {
171
- resolve(utxoHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger, version }));
169
+ resolve(utxoHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger }));
172
170
  });
173
171
  });
174
172
 
@@ -195,7 +193,7 @@ stakePoolProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Log
195
193
  if (params.baseUrl === undefined) throw new Error(`${stakePoolHttpProvider.name}: ${MISSING_URL_PARAM}`);
196
194
 
197
195
  return new Promise<StakePoolProvider>(async (resolve) => {
198
- resolve(stakePoolHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger, version }));
196
+ resolve(stakePoolHttpProvider({ adapter: customHttpFetchAdapter, baseUrl: params.baseUrl, logger }));
199
197
  });
200
198
  });
201
199
 
@@ -2,7 +2,7 @@ import * as envalid from 'envalid';
2
2
  import { ArtilleryContext, FunctionHook, WhileTrueHook } from './artillery';
3
3
  import { Cardano, Paginated, QueryStakePoolsArgs } from '@cardano-sdk/core';
4
4
  import { logger } from '@cardano-sdk/util-dev';
5
- import { stakePoolHttpProvider, version } from '@cardano-sdk/cardano-services-client';
5
+ import { stakePoolHttpProvider } from '@cardano-sdk/cardano-services-client';
6
6
 
7
7
  /**
8
8
  * The context variables shared between all the hooks.
@@ -26,7 +26,7 @@ interface StakePoolSearchVars extends Paginated<Cardano.StakePool> {
26
26
  }
27
27
 
28
28
  const env = envalid.cleanEnv(process.env, { STAKE_POOL_PROVIDER_URL: envalid.str() });
29
- const provider = stakePoolHttpProvider({ baseUrl: env.STAKE_POOL_PROVIDER_URL, logger, version });
29
+ const provider = stakePoolHttpProvider({ baseUrl: env.STAKE_POOL_PROVIDER_URL, logger });
30
30
 
31
31
  const PAGE_SIZE = 20;
32
32
 
@@ -4,7 +4,7 @@ import { ChildProcess, fork } from 'child_process';
4
4
  import { WriteStream, createWriteStream } from 'fs';
5
5
  import { getRandomPort } from 'get-port-please';
6
6
  import { logger } from '@cardano-sdk/util-dev';
7
- import { stakePoolHttpProvider, version } from '@cardano-sdk/cardano-services-client';
7
+ import { stakePoolHttpProvider } from '@cardano-sdk/cardano-services-client';
8
8
  import path from 'path';
9
9
 
10
10
  type StakePoolRecord = Record<string, Cardano.StakePool>;
@@ -127,7 +127,7 @@ describe('StakePoolCompare', () => {
127
127
  };
128
128
 
129
129
  const setupProvider = async (args: string[] = []) => {
130
- const provider = stakePoolHttpProvider({ baseUrl: await startServer(args), logger, version });
130
+ const provider = stakePoolHttpProvider({ baseUrl: await startServer(args), logger });
131
131
 
132
132
  // eslint-disable-next-line no-constant-condition
133
133
  while (true) {
@@ -4,7 +4,7 @@ import { Logger } from 'ts-log';
4
4
  import { MeasurementUtil, getEnv, getLoadTestScheduler } from '../../../src';
5
5
  import { bufferTime, from, tap } from 'rxjs';
6
6
  import { logger } from '@cardano-sdk/util-dev';
7
- import { stakePoolHttpProvider, version } from '@cardano-sdk/cardano-services-client';
7
+ import { stakePoolHttpProvider } from '@cardano-sdk/cardano-services-client';
8
8
 
9
9
  // Example call:
10
10
  /* STAKE_POOL_PROVIDER_URL="http://mhvm:4000/stake-pool" \
@@ -14,7 +14,7 @@ import { stakePoolHttpProvider, version } from '@cardano-sdk/cardano-services-cl
14
14
  */
15
15
 
16
16
  const { STAKE_POOL_PROVIDER_URL } = envalid.cleanEnv(process.env, { STAKE_POOL_PROVIDER_URL: envalid.str() });
17
- const provider = stakePoolHttpProvider({ baseUrl: STAKE_POOL_PROVIDER_URL, logger, version });
17
+ const provider = stakePoolHttpProvider({ baseUrl: STAKE_POOL_PROVIDER_URL, logger });
18
18
  const testLogger: Logger = console;
19
19
  const intermediateResultsInterval = 10_000;
20
20
 
@@ -47,7 +47,7 @@ describe('cache invalidation', () => {
47
47
 
48
48
  afterAll(() => wallet1.wallet.shutdown());
49
49
 
50
- test('cache is invalidated on epoch rollover', async () => {
50
+ test.skip('cache is invalidated on epoch rollover', async () => {
51
51
  const wallet = wallet1.wallet;
52
52
 
53
53
  await walletReady(wallet);
@@ -5,10 +5,8 @@ import {
5
5
  getEnv,
6
6
  getTxConfirmationEpoch,
7
7
  getWallet,
8
- requestCoins,
9
8
  runningAgainstLocalNetwork,
10
9
  submitAndConfirm,
11
- transferCoins,
12
10
  waitForEpoch,
13
11
  walletVariables
14
12
  } from '../../src';
@@ -24,9 +22,6 @@ describe('delegation rewards', () => {
24
22
  let wallet2: PersonalWallet;
25
23
 
26
24
  const initializeWallets = async () => {
27
- const amountFromFaucet = 100_000_000_000n;
28
- const tAdaToSend = 50_000_000n;
29
-
30
25
  ({ wallet: wallet1, providers } = await getWallet({
31
26
  env,
32
27
  logger,
@@ -35,9 +30,6 @@ describe('delegation rewards', () => {
35
30
  }));
36
31
  ({ wallet: wallet2 } = await getWallet({ env, logger, name: 'Receiving Wallet', polling: { interval: 50 } }));
37
32
 
38
- await requestCoins({ coins: amountFromFaucet, wallet: wallet1 });
39
- await transferCoins({ coins: tAdaToSend, fromWallet: wallet1, toWallet: wallet2 });
40
-
41
33
  await waitForWalletStateSettle(wallet1);
42
34
  await waitForWalletStateSettle(wallet2);
43
35
  };
@@ -0,0 +1,132 @@
1
+ import * as envalid from 'envalid';
2
+ import { DockerUtil } from '@cardano-sdk/util-dev';
3
+ import { open, rm } from 'fs/promises';
4
+ import Dockerode from 'dockerode';
5
+ import axios, { AxiosResponse } from 'axios';
6
+ import delay from 'delay';
7
+ import path from 'path';
8
+
9
+ const preventOgmiosStartFile = path.join(__dirname, '..', '..', 'local-network', 'sdk-ipc', 'prevent_ogmios');
10
+
11
+ const docker = new Dockerode();
12
+
13
+ const ogmiosContainer = docker.getContainer('local-network-e2e-cardano-node-ogmios-1');
14
+ const stakePoolProjectorContainer = docker.getContainer('local-network-e2e-stake-pool-projector-1');
15
+
16
+ const createPrevent = async () => {
17
+ const file = await open(preventOgmiosStartFile, 'a');
18
+
19
+ await file.close();
20
+ };
21
+
22
+ const removePrevent = () => rm(preventOgmiosStartFile, { force: true });
23
+
24
+ const killContainer = async (container: DockerUtil.Docker.Container) => {
25
+ // This waits only for the signal to be sent
26
+ await DockerUtil.containerExec(container, ['kill', '1']);
27
+ // Let's wait one more second to ensure the container was shut down
28
+ await delay(1000);
29
+ };
30
+
31
+ describe('projector ogmios connection', () => {
32
+ let stakePoolProjectorUrl: string;
33
+
34
+ const fetchProjectorHealth = async () => {
35
+ let result: AxiosResponse;
36
+
37
+ try {
38
+ result = await axios.post(stakePoolProjectorUrl, {});
39
+ } catch (error) {
40
+ if (axios.isAxiosError(error)) throw new Error(error.message);
41
+
42
+ throw error;
43
+ }
44
+
45
+ return result;
46
+ };
47
+
48
+ const checkProjector = async () => {
49
+ const health = await fetchProjectorHealth();
50
+
51
+ if (!health.data.ok) throw new Error('Projector not healthy');
52
+
53
+ const { blockNo } = health.data.services[0].projectedTip;
54
+ const start = Date.now();
55
+
56
+ // Wait for another block to be projected to ensure projector is working correctly
57
+ while ((await fetchProjectorHealth()).data.services[0].projectedTip.blockNo === blockNo) {
58
+ if (Date.now() - start > 60_000) throw new Error('The projector is not projecting new blocks');
59
+
60
+ await delay(100);
61
+ }
62
+ };
63
+
64
+ const waitProjector = async (skipOkCheck = false, tolerateConnectionErrors = false) => {
65
+ const start = Date.now();
66
+ let projectorReady = false;
67
+
68
+ do {
69
+ try {
70
+ const health = await fetchProjectorHealth();
71
+
72
+ if ('ok' in health.data && (skipOkCheck || health.data.ok)) projectorReady = true;
73
+ } catch (error) {
74
+ if (error && !tolerateConnectionErrors) throw error;
75
+ }
76
+
77
+ if (Date.now() - start > 60_000) throw new Error("The projector can't get ready");
78
+ } while (projectorReady === false);
79
+ };
80
+
81
+ beforeAll(() => {
82
+ const env = envalid.cleanEnv(process.env, { STAKE_POOL_PROJECTOR_URL: envalid.url() });
83
+
84
+ stakePoolProjectorUrl = `${env.STAKE_POOL_PROJECTOR_URL}health`;
85
+ });
86
+
87
+ beforeEach(async () => {
88
+ await removePrevent();
89
+ await checkProjector();
90
+ });
91
+
92
+ afterEach(() => removePrevent());
93
+
94
+ it.skip('projector reconnects after a short delay', async () => {
95
+ await killContainer(ogmiosContainer);
96
+ await waitProjector();
97
+ // This throws because is not able to complete HTTP request
98
+ await checkProjector();
99
+ });
100
+
101
+ // Once the problem which prevent the original test to work is solved
102
+ // the original test can be un-skipped and this one can be removed
103
+ it('projector reconnects after a short delay - alternative', async () => {
104
+ await killContainer(ogmiosContainer);
105
+ // Add a 10" sleep as a quick workaround for HTTP connection issue
106
+ await delay(10_000);
107
+ await waitProjector();
108
+ await checkProjector();
109
+ });
110
+
111
+ it.skip('projector reconnects after a long delay', async () => {
112
+ await createPrevent();
113
+ await killContainer(ogmiosContainer);
114
+ await delay(120_000);
115
+ await removePrevent();
116
+ await waitProjector();
117
+ await checkProjector();
118
+ });
119
+
120
+ it('projector connects to a later started ogmios', async () => {
121
+ await createPrevent();
122
+ await killContainer(ogmiosContainer);
123
+ await killContainer(stakePoolProjectorContainer);
124
+ await waitProjector(true, true);
125
+ await removePrevent();
126
+ // Once the 'projector reconnects after a short delay' problem is solved
127
+ // also remove following line and uncomment next one
128
+ await waitProjector(false, true);
129
+ // await waitProjector();
130
+ await checkProjector();
131
+ });
132
+ });
@@ -3,7 +3,7 @@
3
3
  import * as envalid from 'envalid';
4
4
  import { Cardano, QueryStakePoolsArgs, StakePoolProvider } from '@cardano-sdk/core';
5
5
  import { logger } from '@cardano-sdk/util-dev';
6
- import { stakePoolHttpProvider, version } from '@cardano-sdk/cardano-services-client';
6
+ import { stakePoolHttpProvider } from '@cardano-sdk/cardano-services-client';
7
7
 
8
8
  const stringToRegExEqualsTo = (str: string) => `^${str.replace(/[$()*+.?[\\\]^{|}-]/g, '\\$&')}$`;
9
9
 
@@ -140,7 +140,7 @@ describe('StakePoolProvider', () => {
140
140
 
141
141
  beforeAll(async () => {
142
142
  const env = envalid.cleanEnv(process.env, { STAKE_POOL_PROVIDER_URL: envalid.url() });
143
- const config = { baseUrl: env.STAKE_POOL_PROVIDER_URL, logger, version };
143
+ const config = { baseUrl: env.STAKE_POOL_PROVIDER_URL, logger };
144
144
 
145
145
  provider = stakePoolHttpProvider(config);
146
146
  pools = await fetchAllPools();
@@ -1,15 +1,30 @@
1
1
  // Expose any additional services to be shared with UIs
2
- import { BackgroundServices, adaPriceProperties, logger } from '../util';
2
+ import { BackgroundServices, adaPriceProperties, env, logger } from '../util';
3
+ import { Cardano } from '@cardano-sdk/core';
3
4
  import { adaPriceServiceChannel, walletName } from '../const';
4
5
  import { authenticator } from './authenticator';
5
6
  import { exposeApi, exposeSupplyDistributionTracker } from '@cardano-sdk/web-extension';
6
7
  import { of } from 'rxjs';
7
8
  import { runtime } from 'webextension-polyfill';
9
+ import { stakePoolProviderFactory } from '../../../../src';
8
10
  import { supplyDistributionTrackerReady } from './supplyDistributionTracker';
9
11
 
10
12
  const priceService: BackgroundServices = {
11
13
  adaUsd$: of(2.99),
12
- clearAllowList: authenticator.clear.bind(authenticator)
14
+ clearAllowList: authenticator.clear.bind(authenticator),
15
+ getPoolIds: async (count: number): Promise<Cardano.StakePool[]> => {
16
+ const stakePoolProvider = await stakePoolProviderFactory.create(
17
+ env.STAKE_POOL_PROVIDER,
18
+ env.STAKE_POOL_PROVIDER_PARAMS,
19
+ logger
20
+ );
21
+
22
+ const activePools = await stakePoolProvider.queryStakePools({
23
+ filters: { status: [Cardano.StakePoolStatus.Active] },
24
+ pagination: { limit: count, startAt: 0 }
25
+ });
26
+ return activePools.pageResults.slice(0, count);
27
+ }
13
28
  };
14
29
 
15
30
  exposeApi(
@@ -27,6 +27,17 @@
27
27
  <div>Stake Address: <span id="stakeAddress">-</span></div>
28
28
  <div>Balance: <span id="balance">-</span></div>
29
29
  <div>Network-wide staked: <span id="supplyDistribution">-</span></div>
30
+ <div id="multiDelegation">
31
+ <div class="distribution">
32
+ <h3>Delegation distribution:</h3>
33
+ <ul></ul>
34
+ </div>
35
+ <div class="delegate">
36
+ <p>Available Pools: <span class="pools"></span></p>
37
+ <button>Delegate to multiple pools</button>
38
+ <p class="delegateTxId"></p>
39
+ </div>
40
+ </div>
30
41
  <button id="buildAndSignTx">Build & Sign TX</button>
31
42
  <div>Signature: <span id="signature">-</span></div>
32
43
  <script src="ui.js"></script>
@@ -10,10 +10,16 @@ import {
10
10
  import { adaPriceServiceChannel, getObservableWalletName, userPromptServiceChannel, walletName } from './const';
11
11
  import { bip32Ed25519Factory, keyManagementFactory } from '../../../src';
12
12
 
13
+ import { Cardano } from '@cardano-sdk/core';
13
14
  import { combineLatest, firstValueFrom, of } from 'rxjs';
14
15
  import { runtime } from 'webextension-polyfill';
15
16
  import { setupWallet } from '@cardano-sdk/wallet';
16
17
 
18
+ const delegationConfig = {
19
+ count: 3,
20
+ distribution: [10, 30, 60]
21
+ };
22
+
17
23
  const api: UserPromptService = {
18
24
  allowOrigin(origin) {
19
25
  const container = document.querySelector<HTMLDivElement>('#requestAccess')!;
@@ -66,6 +72,30 @@ combineLatest([supplyDistribution.lovelaceSupply$, supplyDistribution.stake$]).s
66
72
  (document.querySelector('#supplyDistribution')!.textContent = `${stake.live} out of ${lovelaceSupply.total}`)
67
73
  );
68
74
 
75
+ /** Get pools from background service and assign weights */
76
+ const displayPoolIdsAndPreparePortfolio = async (): Promise<{ pool: Cardano.StakePool; weight: number }[]> => {
77
+ const pools = await backgroundServices.getPoolIds(delegationConfig.count);
78
+ const poolsSpan = document.querySelector('#multiDelegation .delegate .pools');
79
+ poolsSpan!.textContent = pools.map(({ id }) => id).join(' ');
80
+ return pools.map((pool, idx) => ({ pool, weight: delegationConfig.distribution[idx] }));
81
+ };
82
+
83
+ /** Build, sign and submit delegation transaction */
84
+ const sendDelegationTx = async (portfolio: { pool: Cardano.StakePool; weight: number }[]): Promise<void> => {
85
+ const pools = portfolio.map(({ pool: { hexId: id }, weight }) => ({ id, weight }));
86
+ const txBuilder = wallet.createTxBuilder();
87
+
88
+ let msg: string;
89
+ try {
90
+ const signedTx = await txBuilder.delegatePortfolio({ pools }).build().sign();
91
+ const txId = await wallet.submitTx(signedTx);
92
+ msg = `TxId: ${txId}`;
93
+ } catch (error) {
94
+ msg = `ERROR delegating: ${JSON.stringify(error)}`;
95
+ }
96
+ document.querySelector('#multiDelegation .delegateTxId')!.textContent = msg;
97
+ };
98
+
69
99
  const setAddresses = ({ address, stakeAddress }: { address: string; stakeAddress: string }): void => {
70
100
  document.querySelector('#address')!.textContent = address;
71
101
  document.querySelector('#stakeAddress')!.textContent = stakeAddress;
@@ -100,6 +130,45 @@ const deactivateWallet = async (): Promise<void> => {
100
130
  clearWalletValues();
101
131
  };
102
132
 
133
+ /**
134
+ * Wallet does not have any active delegations.
135
+ * Show a `<p class="noDelegation">No delegation found</p>`
136
+ */
137
+ const createEmptyDelegationEl = () => {
138
+ const emptyDistribution = document.createElement('p');
139
+ emptyDistribution.classList.add('noDelegation');
140
+ emptyDistribution.textContent = 'No delegation found';
141
+ return emptyDistribution;
142
+ };
143
+
144
+ /**
145
+ * Create a list item for a delegation
146
+ * `<li> <span class="poolId">thePoolId</span> <span class="percent">50</span> </li>`
147
+ */
148
+ const createDelegationLi = (poolId: string, percent: string) => {
149
+ const delegationLi = document.createElement('li');
150
+ const poolIdSpan = document.createElement('span');
151
+ poolIdSpan.classList.add('poolId');
152
+ poolIdSpan.textContent = poolId;
153
+ const delegationPercentageSpan = document.createElement('span');
154
+ delegationPercentageSpan.classList.add('percent');
155
+ delegationPercentageSpan.textContent = percent;
156
+ const separatorSpan = document.createElement('span');
157
+ separatorSpan.textContent = ' - ';
158
+ delegationLi.append(poolIdSpan);
159
+ delegationLi.append(separatorSpan);
160
+ delegationLi.append(delegationPercentageSpan);
161
+ return delegationLi;
162
+ };
163
+
164
+ /** Remove empty delegation message or multi-delegation list items to display new data */
165
+ const cleanupMultidelegationInfo = (multiDelegationDiv: Element) => {
166
+ multiDelegationDiv.querySelector('p.noDelegation')?.remove();
167
+ for (const delegationLi of multiDelegationDiv.querySelectorAll('ul > li')) {
168
+ delegationLi.remove();
169
+ }
170
+ };
171
+
103
172
  const walletManager = new WalletManagerUi({ walletName }, { logger, runtime });
104
173
  // Wallet object does not change when wallets are activated/deactivated.
105
174
  // Instead, it's observable properties emit from the currently active wallet.
@@ -108,6 +177,19 @@ const wallet = walletManager.wallet;
108
177
  // Wallet can be subscribed can be used even before it is actually created.
109
178
  wallet.addresses$.subscribe(([{ address, rewardAccount }]) => setAddresses({ address, stakeAddress: rewardAccount }));
110
179
  wallet.balance.utxo.available$.subscribe(({ coins }) => setBalance(coins.toString()));
180
+ wallet.delegation.distribution$.subscribe((delegationDistrib) => {
181
+ const multiDelegationDiv = document.querySelector('#multiDelegation .distribution');
182
+ cleanupMultidelegationInfo(multiDelegationDiv!);
183
+
184
+ if (delegationDistrib.size === 0) {
185
+ multiDelegationDiv?.appendChild(createEmptyDelegationEl());
186
+ } else {
187
+ const distributionUl = multiDelegationDiv?.querySelector('ul');
188
+ for (const [poolId, delegation] of delegationDistrib) {
189
+ distributionUl?.appendChild(createDelegationLi(poolId, (delegation.percentage * 100).toString()));
190
+ }
191
+ }
192
+ });
111
193
 
112
194
  const createWallet = async (accountIndex: number) => {
113
195
  clearWalletValues();
@@ -142,6 +224,11 @@ document.querySelector('#activateWallet1')!.addEventListener('click', async () =
142
224
  document.querySelector('#activateWallet2')!.addEventListener('click', async () => await createWallet(1));
143
225
  document.querySelector('#deactivateWallet')!.addEventListener('click', async () => await deactivateWallet());
144
226
  document.querySelector('#destroyWallet')!.addEventListener('click', async () => await destroyWallet());
227
+ document.querySelector('#multiDelegation .delegate button')!.addEventListener('click', async () => {
228
+ const poolsAndWeights = await displayPoolIdsAndPreparePortfolio();
229
+ // multi-delegate with 10%, 30%, 60% distribution
230
+ await sendDelegationTx(poolsAndWeights);
231
+ });
145
232
 
146
233
  document.querySelector('#buildAndSignTx')!.addEventListener('click', async () => {
147
234
  const [{ address: ownAddress }] = await firstValueFrom(wallet.addresses$);