chore: add delegate stake example
Abstract common examples logic Refs #202
Abstract common examples logic Refs #202
/* eslint-disable no-magic-numbers */
/* eslint-disable camelcase */
/* eslint-disable new-cap */
/* eslint-disable no-console */
import delay from "delay";
import * as NaCl from "tweetnacl";
import axios from "axios";
const logger = console;
const httpClient = axios.create({
baseURL: "http://localhost:8080",
});
const network_identifier = {
blockchain: "cardano",
network: "testnet",
};
const generateKeys = (secretKey?: string) =>
secretKey
? NaCl.sign.keyPair.fromSecretKey(Buffer.from(secretKey, "hex"))
: NaCl.sign.keyPair();
const constructionDerive = async (
publicKey: string,
addressType?: string
): Promise<string> => {
logger.info(
`[constructionDerive] Fetching an address for pub key ${publicKey}`
);
const request = {
network_identifier,
public_key: {
hex_bytes: publicKey,
curve_type: "edwards25519",
},
metadata: {},
};
if (addressType) request.metadata = { address_type: addressType };
const response = await httpClient.post("/construction/derive", request);
const address = response.data.address;
logger.debug(`[constructionDerive] Retrieved address ${address}`);
return address;
};
const waitForBalanceToBe = async (
address: string,
cond: (param: any) => boolean
) => {
let fetchAccountBalance;
do {
const response = await httpClient.post("/account/balance", {
network_identifier,
account_identifier: {
address,
},
});
if (cond(response.data)) {
const [balance] = response.data.balances;
logger.info(
`[waitForBalanceToBe] Funds found! ${balance.value} ${balance.currency.symbol}`
);
fetchAccountBalance = response.data;
} else {
logger.debug(
"[waitForBalanceToBe] Condition not met, waiting for a few seconds."
);
await delay(30 * 1000);
}
} while (!fetchAccountBalance);
return fetchAccountBalance;
};
const constructionPreprocess = async (
operations: any,
relative_ttl: number
) => {
const response = await httpClient.post("/construction/preprocess", {
network_identifier,
operations,
metadata: { relative_ttl },
});
return response.data.options;
};
const constructionMetadata = async (options: any) => {
const response = await httpClient.post("/construction/metadata", {
network_identifier,
options,
});
return response.data.metadata;
};
const buildOperation = (
unspents: any,
address: string,
destination: string
) => {
const inputs = unspents.coins.map((coin: any, index: number) => {
const operation = {
operation_identifier: {
index,
network_index: 0,
},
related_operations: [],
type: "input",
status: "success",
account: {
address,
metadata: {},
},
amount: coin.amount,
coin_change: {
coin_identifier: coin.coin_identifier,
coin_action: "coin_created",
},
};
operation.amount.value = `-${operation.amount.value}`;
return operation;
});
// TODO: No proper fees estimation is being done (it should be transaction size based)
const totalBalance = BigInt(unspents.balances[0].value);
const outputAmount = (totalBalance * BigInt(95)) / BigInt(100);
const outputs = [
{
operation_identifier: {
index: inputs.length,
network_index: 0,
},
related_operations: [],
type: "output",
status: "success",
account: {
address: destination,
metadata: {},
},
amount: {
value: outputAmount.toString(),
currency: {
symbol: "ADA",
decimals: 6,
},
metadata: {},
},
},
];
return {
network_identifier,
operations: inputs.concat(outputs),
};
};
const constructionPayloads = async (payload: any) => {
const response = await httpClient.post("/construction/payloads", {
network_identifier,
...payload,
});
return response.data;
};
const signPayloads = (payloads: any, keys: NaCl.SignKeyPair) =>
payloads.map((signing_payload: any) => ({
signing_payload,
public_key: {
hex_bytes: Buffer.from(keys.publicKey).toString("hex"),
curve_type: "edwards25519",
},
signature_type: "ed25519",
hex_bytes: Buffer.from(
NaCl.sign.detached(
Buffer.from(signing_payload.hex_bytes, "hex"),
keys.secretKey
)
).toString("hex"),
}));
const constructionCombine = async (
unsigned_transaction: any,
signatures: any
) => {
const response = await httpClient.post("/construction/combine", {
network_identifier,
unsigned_transaction,
signatures,
});
return response.data;
};
const constructionSubmit = async (signed_transaction: any) => {
const response = await httpClient.post("/construction/submit", {
network_identifier,
signed_transaction,
});
return response.data;
};
export {
constructionDerive,
constructionPreprocess,
constructionMetadata,
constructionPayloads,
constructionCombine,
/* eslint-disable no-magic-numbers */
/* eslint-disable camelcase */
/* eslint-disable new-cap */
/* eslint-disable no-console */
import {
constructionDerive,
constructionPreprocess,
constructionMetadata,
constructionPayloads,
constructionCombine,
constructionSubmit,
signPayloads,
waitForBalanceToBe,
buildOperation,
generateKeys,
} from "./commons";
const logger = console;
const PRIVATE_KEY =
"41d9523b87b9bd89a4d07c9b957ae68a7472d8145d7956a692df1a8ad91957a2c117d9dd874447f47306f50a650f1e08bf4bec2cfcb2af91660f23f2db912977";
const SEND_FUNDS_ADDRESS =
"addr1qqr585tvlc7ylnqvz8pyqwauzrdu0mxag3m7q56grgmgu7sxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknsug829n";
const PRIVATE_STAKING_KEY =
"nqDmxvW0ytblMFnEbMFO8rVgl5V/pYnso/TPOyWz3vylms3kaRPDAg49S/rYyJh+eb+e0X3jeqCBhXgAHNjLCA==";
const STAKE_POOL_KEY_HASH =
"22a8dc80b6fb4852150960c2e3896fa0a03498f514afc474c33152b6";
const buildRegistrationOperation = (
stakingKey: string,
currentIndex: number
) => ({
operation_identifier: { index: currentIndex + 1 },
type: "stakeKeyRegistration",
status: "success",
metadata: {
staking_credential: {
hex_bytes: stakingKey,
curve_type: "edwards25519",
},
},
});
const buildDelegationOperation = (
stakingKey: string,
currentIndex: number,
poolKeyHash: string
) => ({
operation_identifier: {
index: currentIndex + 1,
},
type: "stakeDelegation",
status: "success",
metadata: {
staking_credential: {
hex_bytes: stakingKey,
curve_type: "edwards25519",
},
pool_key_hash: poolKeyHash,
},
});
const doRun = async (): Promise<void> => {
const keys = generateKeys(PRIVATE_KEY);
logger.info(
`[doRun] secretKey ${Buffer.from(keys.secretKey).toString("hex")}`
);
const publicKey = Buffer.from(keys.publicKey).toString("hex");
const stakingKey = Buffer.from(
generateKeys(PRIVATE_STAKING_KEY).publicKey
).toString("hex");
logger.info(`[doRun] secretKey ${stakingKey}`);
const address = await constructionDerive(publicKey);
const unspents = await waitForBalanceToBe(
address,
(response) => response.coins.length !== 0
);
const builtOperations = buildOperation(unspents, address, SEND_FUNDS_ADDRESS);
const totalOperations = builtOperations.operations.length;
const builtRegistrationOperation = buildRegistrationOperation(
stakingKey,
totalOperations
);
const builtDelegationOperation = buildDelegationOperation(
stakingKey,
totalOperations + 1,
STAKE_POOL_KEY_HASH
);
builtOperations.operations.push(builtRegistrationOperation);
builtOperations.operations.push(builtDelegationOperation);
const preprocess = await constructionPreprocess(builtOperations, 1000);
const metadata = await constructionMetadata(preprocess);
const payloads = await constructionPayloads({
operations: builtOperations.operations,
metadata,
});
const signatures = signPayloads(payloads.payloads, keys);
const combined = await constructionCombine(
payloads.unsigned_transaction,
signatures
);
logger.info(`[doRun] signed transaction is ${combined.signed_transaction}`);
const hashResponse = await constructionSubmit(combined.signed_transaction);
logger.info(
`[doRun] transaction with hash ${hashResponse.transaction_identifier.hash} sent`
);
};
doRun()
.then(() => logger.info("Stake Delegation finished"))
.catch(console.error);
"name": "cardano-rosetta-examples",
"license": "Apache-2.0",
"scripts": {
"send-transactions-example": "ts-node ./send-transaction-example.ts"
"send-transactions-example": "ts-node ./send-transaction-example.ts",
"register-stake-key-example": "ts-node ./register-stake-key-example.ts"
},
"dependencies": {
"@emurgo/cardano-serialization-lib-nodejs": "2.0.0",
"@types/node": "^14.11.5",
"@types/node": "^14.14.10",
"@types/temp-write": "^4.0.0",
"axios": "^0.19.2",
"delay": "^4.4.0",
/* eslint-disable camelcase */
/* eslint-disable new-cap */
/* eslint-disable no-console */
import delay from 'delay';
import * as NaCl from 'tweetnacl';
import axios from 'axios';
import {
constructionDerive,
constructionPreprocess,
constructionMetadata,
constructionPayloads,
constructionCombine,
constructionSubmit,
signPayloads,
waitForBalanceToBe,
buildOperation,
generateKeys,
} from "./commons";
const logger = console;
const httpClient = axios.create({
baseURL: "http://localhost:8080",
});
const PRIVATE_KEY =
"41d9523b87b9bd89a4d07c9b957ae68a7472d8145d7956a692df1a8ad91957a2c117d9dd874447f47306f50a650f1e08bf4bec2cfcb2af91660f23f2db912977";
const SEND_FUNDS_ADDRESS =
"addr1qqr585tvlc7ylnqvz8pyqwauzrdu0mxag3m7q56grgmgu7sxu2hyfhlkwuxupa9d5085eunq2qywy7hvmvej456flknsug829n";
// FIXME: look for this using the service
const network_identifier = {
blockchain: "cardano",
network: "testnet",
};
const generateKeys = (secretKey?: string) =>
secretKey
? NaCl.sign.keyPair.fromSecretKey(Buffer.from(secretKey, "hex"))
: NaCl.sign.keyPair();
const constructionDerive = async (publicKey: string): Promise<string> => {
logger.info(
`[constructionDerive] Fetching an address for pub key ${publicKey}`
);
const response = await httpClient.post("/construction/derive", {
network_identifier,
public_key: {
hex_bytes: publicKey,
curve_type: "edwards25519",
},
metadata: {},
});
const address = response.data.address;
logger.debug(`[constructionDerive] Retrieved address ${address}`);
return address;
};
const waitForBalanceToBe = async (
address: string,
cond: (param: any) => boolean
) => {
let fetchAccountBalance;
do {
const response = await httpClient.post("/account/balance", {
network_identifier,
account_identifier: {
address,
},
});
if (cond(response.data)) {
const [balance] = response.data.balances;
logger.info(
`[waitForBalanceToBe] Funds found! ${balance.value} ${balance.currency.symbol}`
);
fetchAccountBalance = response.data;
} else {
logger.debug(
"[waitForBalanceToBe] Condition not met, waiting for a few seconds."
);
await delay(30 * 1000);
}
} while (!fetchAccountBalance);
return fetchAccountBalance;
};
const constructionPreprocess = async (
operations: any,
relative_ttl: number
) => {
const response = await httpClient.post("/construction/preprocess", {
network_identifier,
operations,
metadata: { relative_ttl },
});
return response.data.options;
};
const constructionMetadata = async (options: any) => {
const response = await httpClient.post("/construction/metadata", {
network_identifier,
options,
});
return response.data.metadata;
};
const buildOperation = (
unspents: any,
address: string,
destination: string
) => {
const inputs = unspents.coins.map((coin: any, index: number) => {
const operation = {
operation_identifier: {
index,
network_index: 0,
},
related_operations: [],
type: "input",
status: "success",
account: {
address,
metadata: {},
},
amount: coin.amount,
coin_change: {
coin_identifier: coin.coin_identifier,
coin_action: "coin_created",
},
};
operation.amount.value = `-${operation.amount.value}`;
return operation;
});
// TODO: No proper fees estimation is being done (it should be transaction size based)
const totalBalance = BigInt(unspents.balances[0].value);
const outputAmount = (totalBalance * BigInt(95)) / BigInt(100);
const outputs = [
{
operation_identifier: {
index: inputs.length,
network_index: 0,
},
related_operations: [],
type: "output",
status: "success",
account: {
address: destination,
metadata: {},
},
amount: {
value: outputAmount.toString(),
currency: {
symbol: "ADA",
decimals: 6,
},
metadata: {},
},
},
];
return {
network_identifier,
operations: inputs.concat(outputs),
};
};
const constructionPayloads = async (payload: any) => {
const response = await httpClient.post("/construction/payloads", payload);
return response.data;
};
const signPayloads = (payloads: any, keys: NaCl.SignKeyPair) =>
payloads.map((signing_payload: any) => ({
signing_payload,
public_key: {
hex_bytes: Buffer.from(keys.publicKey).toString("hex"),
curve_type: "edwards25519",
},
signature_type: "ed25519",
hex_bytes: Buffer.from(
NaCl.sign.detached(
Buffer.from(signing_payload.hex_bytes, "hex"),
keys.secretKey
)
).toString("hex"),
}));
const constructionCombine = async (
unsigned_transaction: any,
signatures: any
) => {
const response = await httpClient.post("/construction/combine", {
network_identifier,
unsigned_transaction,
signatures,
});
return response.data;
};
const constructionSubmit = async (signed_transaction: any) => {
https://github.com/input-output-hk/cardano-haskell/pull/38
Needs latest prettyprinter version.
Needs latest prettyprinter version.
https://github.com/input-output-hk/cardano-haskell/pull/38
In response to review feedback: https://github.com/input-output-hk/cardano-wallet/pull/2451#discussion_r561029734
Three changes that the hydra eval faster. In order of importance: 1) Split `native` jobs into `linux` and `darwin` Reduces the number of times hydra restarts the eval process because memory is running low. When both linux and darwin versions are in the same attribute the evaluation runs out of memory more often as it alternates between evaluating linux and darwin jobs. Grouping all the linux and darwin jobs together reduces the number of times the memory limit is reached. This is makes the "no change" eval more than 4x faster (270s vs 1300s). 2) Avoid duplicate `cabal configure` To get a list of package names in the project we call `cabalProject`, before using those names to provide the overrides for another call to `cabalProject`. `cabalProjectLocal` is not the same for the two calls and as a result `cabal configure` is run twice (and it is very slow for this project. Passing the same `cabalProjectLocal` value to both calls should improve evaluation greatly when changes are made to the `cabal.project` or `.cabal` files. 3) Include jobs to pin plan-nix Pinning the `plan-nix` output means it is copied to the hydra cache. This may not improve eval on the hydra machine itself, it will make testing evaluation on other machines faster (since they will have access to the cached plans).
In response to review feedback: https://github.com/input-output-hk/cardano-wallet/pull/2451#discussion_r560856424
Bump dependencies