@cardano-sdk/e2e 0.14.2 → 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.
- package/.env.example +3 -0
- package/CHANGELOG.md +25 -0
- package/dist/cjs/factories.d.ts.map +1 -1
- package/dist/cjs/factories.js +5 -1
- package/dist/cjs/factories.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/factories.d.ts.map +1 -1
- package/dist/esm/factories.js +5 -1
- package/dist/esm/factories.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/docker-compose.yml +20 -4
- package/local-network/scripts/cardano-node-ogmios.sh +13 -0
- package/local-network/scripts/mint-handles.sh +1 -0
- package/local-network/sdk-ipc/README.md +5 -0
- package/package.json +20 -19
- package/src/factories.ts +7 -1
- package/test/long-running/cache-invalidation.test.ts +1 -1
- package/test/long-running/delegation-rewards.test.ts +7 -9
- package/test/long-running/projector-ogmios-connection.test.ts +132 -0
- package/test/wallet/PersonalWallet/delegation.test.ts +2 -2
- package/test/wallet/PersonalWallet/delegationDistribution.test.ts +42 -78
- package/test/wallet/PersonalWallet/handle.test.ts +1 -1
- package/test/web-extension/extension/background/services.ts +17 -2
- package/test/web-extension/extension/ui.html +11 -0
- package/test/web-extension/extension/ui.ts +87 -0
- package/test/web-extension/extension/util.ts +4 -1
- package/test/web-extension/specs/wallet.spec.ts +51 -2
package/docker-compose.yml
CHANGED
|
@@ -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
|
-
|
|
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"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardano-sdk/e2e",
|
|
3
|
-
"version": "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.
|
|
85
|
-
"@cardano-sdk/cardano-services-client": "~0.
|
|
86
|
-
"@cardano-sdk/core": "~0.
|
|
87
|
-
"@cardano-sdk/crypto": "~0.1.
|
|
88
|
-
"@cardano-sdk/hardware-ledger": "~0.2.
|
|
89
|
-
"@cardano-sdk/key-management": "~0.7.
|
|
90
|
-
"@cardano-sdk/ogmios": "~0.12.
|
|
91
|
-
"@cardano-sdk/tx-construction": "~0.
|
|
92
|
-
"@cardano-sdk/util": "~0.
|
|
93
|
-
"@cardano-sdk/util-dev": "~0.13.
|
|
94
|
-
"@cardano-sdk/util-rxjs": "~0.
|
|
95
|
-
"@cardano-sdk/wallet": "~0.
|
|
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.
|
|
126
|
-
"@cardano-sdk/projection": "~0.6.
|
|
127
|
-
"@cardano-sdk/projection-typeorm": "~0.3.
|
|
128
|
-
"@cardano-sdk/web-extension": "~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": "^
|
|
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": "
|
|
177
|
+
"gitHead": "46029ae65c25ce6cfef394249c7ff5a32259757f"
|
|
177
178
|
}
|
package/src/factories.ts
CHANGED
|
@@ -100,7 +100,13 @@ assetProviderFactory.register(HTTP_PROVIDER, async (params: any, logger: Logger)
|
|
|
100
100
|
if (params.baseUrl === undefined) throw new Error(`${assetInfoHttpProvider.name}: ${MISSING_URL_PARAM}`);
|
|
101
101
|
|
|
102
102
|
return new Promise<AssetProvider>(async (resolve) => {
|
|
103
|
-
resolve(
|
|
103
|
+
resolve(
|
|
104
|
+
assetInfoHttpProvider({
|
|
105
|
+
adapter: customHttpFetchAdapter,
|
|
106
|
+
baseUrl: params.baseUrl,
|
|
107
|
+
logger
|
|
108
|
+
})
|
|
109
|
+
);
|
|
104
110
|
});
|
|
105
111
|
});
|
|
106
112
|
|
|
@@ -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
|
};
|
|
@@ -70,7 +62,13 @@ describe('delegation rewards', () => {
|
|
|
70
62
|
|
|
71
63
|
const submitDelegationTx = async () => {
|
|
72
64
|
logger.info(`Creating delegation tx at epoch #${(await firstValueFrom(wallet1.currentEpoch$)).epochNo}`);
|
|
73
|
-
const { tx: signedTx } = await wallet1
|
|
65
|
+
const { tx: signedTx } = await wallet1
|
|
66
|
+
.createTxBuilder()
|
|
67
|
+
.delegatePortfolio({
|
|
68
|
+
pools: [{ id: Cardano.PoolIdHex(Cardano.PoolId.toKeyHash(poolId)), weight: 1 }]
|
|
69
|
+
})
|
|
70
|
+
.build()
|
|
71
|
+
.sign();
|
|
74
72
|
await wallet1.submitTx(signedTx);
|
|
75
73
|
const { epochNo } = await firstValueFrom(wallet1.currentEpoch$);
|
|
76
74
|
logger.info(`Delegation tx ${signedTx.id} submitted at epoch #${epochNo}`);
|
|
@@ -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
|
+
});
|
|
@@ -113,7 +113,7 @@ describe('PersonalWallet/delegation', () => {
|
|
|
113
113
|
|
|
114
114
|
const { tx } = await txBuilder
|
|
115
115
|
.addOutput(await txBuilder.buildOutput().address(destAddresses).coin(tx1OutputCoins).build())
|
|
116
|
-
.
|
|
116
|
+
.delegatePortfolio({ pools: [{ id: Cardano.PoolIdHex(Cardano.PoolId.toKeyHash(poolId)), weight: 1 }] })
|
|
117
117
|
.build()
|
|
118
118
|
.sign();
|
|
119
119
|
await sourceWallet.submitTx(tx);
|
|
@@ -163,7 +163,7 @@ describe('PersonalWallet/delegation', () => {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// Make a 2nd tx with key de-registration
|
|
166
|
-
const { tx: txDeregisterSigned } = await sourceWallet.createTxBuilder().
|
|
166
|
+
const { tx: txDeregisterSigned } = await sourceWallet.createTxBuilder().delegatePortfolio(null).build().sign();
|
|
167
167
|
await sourceWallet.submitTx(txDeregisterSigned);
|
|
168
168
|
await waitForTx(sourceWallet, txDeregisterSigned.id);
|
|
169
169
|
const tx2ConfirmedState = await getWalletStateSnapshot(sourceWallet);
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { AddressType } from '@cardano-sdk/key-management';
|
|
2
1
|
import { Cardano } from '@cardano-sdk/core';
|
|
3
2
|
import { DelegatedStake, PersonalWallet, createUtxoBalanceByAddressTracker } from '@cardano-sdk/wallet';
|
|
4
3
|
import { MINUTE, firstValueFromTimed, getWallet, submitAndConfirm, walletReady } from '../../../src';
|
|
@@ -6,29 +5,17 @@ import { Observable, filter, firstValueFrom, map, tap } from 'rxjs';
|
|
|
6
5
|
import { Percent } from '@cardano-sdk/util';
|
|
7
6
|
import { createLogger } from '@cardano-sdk/util-dev';
|
|
8
7
|
import { getEnv, walletVariables } from '../../../src/environment';
|
|
9
|
-
import delay from 'delay';
|
|
10
8
|
|
|
11
9
|
const env = getEnv(walletVariables);
|
|
12
10
|
const logger = createLogger();
|
|
13
11
|
const TEST_FUNDS = 1_000_000_000n;
|
|
12
|
+
const POOLS_COUNT = 5;
|
|
14
13
|
const distributionMessage = 'ObservableWallet.delegation.distribution$:';
|
|
15
14
|
|
|
16
|
-
const deriveStakeKeys = async (wallet: PersonalWallet) => {
|
|
17
|
-
await walletReady(wallet, 0n);
|
|
18
|
-
// Add 4 new addresses with different stake keys.
|
|
19
|
-
for (let i = 1; i < 5; ++i) {
|
|
20
|
-
await wallet.keyAgent.deriveAddress({ index: 0, type: AddressType.External }, i);
|
|
21
|
-
}
|
|
22
|
-
// Allow status tracker to change status with debounce.
|
|
23
|
-
// Otherwise the updates are be debounced and next calls find the wallet ready before it had a chance to update the status.
|
|
24
|
-
await delay(2);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
15
|
/** Distribute the wallet funds evenly across all its addresses */
|
|
28
|
-
const
|
|
16
|
+
const fundWallet = async (wallet: PersonalWallet) => {
|
|
29
17
|
await walletReady(wallet, 0n);
|
|
30
18
|
const addresses = await firstValueFrom(wallet.addresses$);
|
|
31
|
-
expect(addresses.length).toBeGreaterThan(1);
|
|
32
19
|
|
|
33
20
|
// Check that we have enough funds. Otherwise, fund it from wallet account at index 0
|
|
34
21
|
let { coins: totalCoins } = await firstValueFrom(wallet.balance.utxo.available$);
|
|
@@ -50,22 +37,6 @@ const distributeFunds = async (wallet: PersonalWallet) => {
|
|
|
50
37
|
await walletReady(wallet);
|
|
51
38
|
totalCoins = (await firstValueFrom(wallet.balance.utxo.available$)).coins;
|
|
52
39
|
}
|
|
53
|
-
|
|
54
|
-
const coinsPerAddress = totalCoins / BigInt(addresses.length);
|
|
55
|
-
|
|
56
|
-
const txBuilder = wallet.createTxBuilder();
|
|
57
|
-
|
|
58
|
-
logger.info(`Sending ${coinsPerAddress} to the ${addresses.length - 1} derived addresses`);
|
|
59
|
-
// The first one was generated when the wallet was created.
|
|
60
|
-
for (let i = 1; i < addresses.length; ++i) {
|
|
61
|
-
const derivedAddress = addresses[i];
|
|
62
|
-
logger.info('Funding', derivedAddress.address, coinsPerAddress);
|
|
63
|
-
logger.info(derivedAddress.rewardAccount);
|
|
64
|
-
txBuilder.addOutput(txBuilder.buildOutput().address(derivedAddress.address).coin(coinsPerAddress).toTxOut());
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const { tx: signedTx } = await txBuilder.build().sign();
|
|
68
|
-
await submitAndConfirm(wallet, signedTx);
|
|
69
40
|
};
|
|
70
41
|
|
|
71
42
|
/** await for rewardAccounts$ to be registered, unregistered, as defined in states */
|
|
@@ -97,7 +68,7 @@ const deregisterAllStakeKeys = async (wallet: PersonalWallet): Promise<void> =>
|
|
|
97
68
|
} catch {
|
|
98
69
|
// Some stake keys are registered. Deregister them
|
|
99
70
|
const txBuilder = wallet.createTxBuilder();
|
|
100
|
-
txBuilder.
|
|
71
|
+
txBuilder.delegatePortfolio(null);
|
|
101
72
|
const { tx: deregTx } = await txBuilder.build().sign();
|
|
102
73
|
await submitAndConfirm(wallet, deregTx);
|
|
103
74
|
|
|
@@ -109,36 +80,44 @@ const deregisterAllStakeKeys = async (wallet: PersonalWallet): Promise<void> =>
|
|
|
109
80
|
}
|
|
110
81
|
};
|
|
111
82
|
|
|
112
|
-
const getPoolIds = async (wallet: PersonalWallet
|
|
83
|
+
const getPoolIds = async (wallet: PersonalWallet): Promise<Cardano.StakePool[]> => {
|
|
113
84
|
const activePools = await wallet.stakePoolProvider.queryStakePools({
|
|
114
85
|
filters: { status: [Cardano.StakePoolStatus.Active] },
|
|
115
|
-
pagination: { limit:
|
|
86
|
+
pagination: { limit: POOLS_COUNT, startAt: 0 }
|
|
116
87
|
});
|
|
117
|
-
expect(activePools.pageResults.length).toBeGreaterThanOrEqual(
|
|
118
|
-
return Array.from({ length:
|
|
88
|
+
expect(activePools.pageResults.length).toBeGreaterThanOrEqual(POOLS_COUNT);
|
|
89
|
+
return Array.from({ length: POOLS_COUNT }).map((_, index) => activePools.pageResults[index]);
|
|
119
90
|
};
|
|
120
91
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
92
|
+
/** Delegate to unique POOLS_COUNT pools. Use even distribution as default. */
|
|
93
|
+
const delegateToMultiplePools = async (
|
|
94
|
+
wallet: PersonalWallet,
|
|
95
|
+
weights = Array.from({ length: POOLS_COUNT }).map(() => 1)
|
|
96
|
+
) => {
|
|
97
|
+
const poolIds = await getPoolIds(wallet);
|
|
98
|
+
const portfolio: Pick<Cardano.Cip17DelegationPortfolio, 'pools'> = {
|
|
99
|
+
pools: poolIds.map(({ hexId: id }, idx) => ({ id, weight: weights[idx] }))
|
|
100
|
+
};
|
|
101
|
+
logger.debug('Delegating portfolio', portfolio);
|
|
102
|
+
|
|
103
|
+
const { tx } = await wallet.createTxBuilder().delegatePortfolio(portfolio).build().sign();
|
|
104
|
+
await submitAndConfirm(wallet, tx);
|
|
105
|
+
return poolIds;
|
|
106
|
+
};
|
|
131
107
|
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
const delegateAllToSinglePool = async (wallet: PersonalWallet): Promise<void> => {
|
|
109
|
+
// This is a negative testcase, simulating an HD wallet that has multiple stake keys delegated
|
|
110
|
+
// to the same stake pool. txBuilder.delegatePortfolio does not support this scenario.
|
|
111
|
+
const [{ id: poolId }] = await getPoolIds(wallet);
|
|
112
|
+
const txBuilder = wallet.createTxBuilder();
|
|
113
|
+
const rewardAccounts = await firstValueFrom(wallet.delegation.rewardAccounts$);
|
|
114
|
+
txBuilder.partialTxBody.certificates = rewardAccounts.map(({ address }) =>
|
|
115
|
+
Cardano.createDelegationCert(address, poolId)
|
|
134
116
|
);
|
|
135
117
|
|
|
136
|
-
|
|
137
|
-
// Artificially add the certificates in TxBuilder. An api improvement will make the UX better
|
|
138
|
-
txBuilder.partialTxBody.certificates = [...stakeKeyRegCertificates, ...delegationCertificates];
|
|
118
|
+
logger.debug(`Delegating all stake keys to pool ${poolId}`);
|
|
139
119
|
const { tx } = await txBuilder.build().sign();
|
|
140
120
|
await submitAndConfirm(wallet, tx);
|
|
141
|
-
return poolIds;
|
|
142
121
|
};
|
|
143
122
|
|
|
144
123
|
describe('PersonalWallet/delegationDistribution', () => {
|
|
@@ -146,9 +125,8 @@ describe('PersonalWallet/delegationDistribution', () => {
|
|
|
146
125
|
|
|
147
126
|
beforeAll(async () => {
|
|
148
127
|
wallet = (await getWallet({ env, idx: 3, logger, name: 'Wallet', polling: { interval: 50 } })).wallet;
|
|
149
|
-
await
|
|
128
|
+
await fundWallet(wallet);
|
|
150
129
|
await deregisterAllStakeKeys(wallet);
|
|
151
|
-
await distributeFunds(wallet);
|
|
152
130
|
});
|
|
153
131
|
|
|
154
132
|
afterAll(() => {
|
|
@@ -158,19 +136,16 @@ describe('PersonalWallet/delegationDistribution', () => {
|
|
|
158
136
|
it('reports observable wallet multi delegation as delegationDistribution by pool', async () => {
|
|
159
137
|
await walletReady(wallet);
|
|
160
138
|
|
|
161
|
-
const walletAddresses = await firstValueFromTimed(wallet.addresses$);
|
|
162
|
-
const rewardAccounts = await firstValueFrom(wallet.delegation.rewardAccounts$);
|
|
163
|
-
|
|
164
|
-
expect(rewardAccounts.length).toBe(5);
|
|
165
|
-
|
|
166
139
|
// No stake distribution initially
|
|
167
140
|
const delegationDistribution = await firstValueFrom(wallet.delegation.distribution$);
|
|
168
141
|
logger.info('Empty delegation distribution initially');
|
|
169
142
|
expect(delegationDistribution).toEqual(new Map());
|
|
170
143
|
|
|
171
144
|
const poolIds = await delegateToMultiplePools(wallet);
|
|
172
|
-
|
|
173
|
-
await
|
|
145
|
+
const walletAddresses = await firstValueFromTimed(wallet.addresses$);
|
|
146
|
+
const rewardAccounts = await firstValueFrom(wallet.delegation.rewardAccounts$);
|
|
147
|
+
|
|
148
|
+
expect(rewardAccounts.length).toBe(POOLS_COUNT);
|
|
174
149
|
|
|
175
150
|
// Check that reward addresses were delegated
|
|
176
151
|
await walletReady(wallet);
|
|
@@ -204,20 +179,11 @@ describe('PersonalWallet/delegationDistribution', () => {
|
|
|
204
179
|
|
|
205
180
|
expect([...actualDelegationDistribution.values()]).toEqual(expectedDelegationDistribution);
|
|
206
181
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
txBuilder
|
|
213
|
-
.buildOutput()
|
|
214
|
-
.address(walletAddresses[walletAddresses.length - 1].address)
|
|
215
|
-
.coin(totalCoins - 2_000_000n) // leave some behind for fees
|
|
216
|
-
.toTxOut()
|
|
217
|
-
)
|
|
218
|
-
.build()
|
|
219
|
-
.sign();
|
|
220
|
-
await submitAndConfirm(wallet, txMoveFunds);
|
|
182
|
+
// Delegate so that last address has all funds
|
|
183
|
+
await delegateToMultiplePools(
|
|
184
|
+
wallet,
|
|
185
|
+
Array.from({ length: POOLS_COUNT }).map((_, idx) => (POOLS_COUNT === idx + 1 ? 1 : 0))
|
|
186
|
+
);
|
|
221
187
|
|
|
222
188
|
let simplifiedDelegationDistribution: Partial<DelegatedStake>[] = await firstValueFrom(
|
|
223
189
|
wallet.delegation.distribution$.pipe(
|
|
@@ -246,9 +212,7 @@ describe('PersonalWallet/delegationDistribution', () => {
|
|
|
246
212
|
);
|
|
247
213
|
|
|
248
214
|
// Delegate all reward accounts to the same pool. delegationDistribution$ should have 1 entry with 100% distribution
|
|
249
|
-
|
|
250
|
-
const { tx: txDelegateTo1Pool } = await txBuilder.delegate(poolIds[0].id).build().sign();
|
|
251
|
-
await submitAndConfirm(wallet, txDelegateTo1Pool);
|
|
215
|
+
await delegateAllToSinglePool(wallet);
|
|
252
216
|
simplifiedDelegationDistribution = await firstValueFrom(
|
|
253
217
|
wallet.delegation.distribution$.pipe(
|
|
254
218
|
tap((distribution) => {
|
|
@@ -68,7 +68,7 @@ describe('Ada handle', () => {
|
|
|
68
68
|
let policyScript: Cardano.NativeScript;
|
|
69
69
|
let assetIds: Cardano.AssetId[];
|
|
70
70
|
|
|
71
|
-
const assetNames = ['
|
|
71
|
+
const assetNames = ['68616e646c6531', '68616e646c6532'];
|
|
72
72
|
let walletAddress: Cardano.PaymentAddress;
|
|
73
73
|
const coins = 2_000_000n; // number of coins to use in each transaction
|
|
74
74
|
|
|
@@ -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>
|