From c993b3dbde248d9965ed6ec087378dad59190cb7 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:58:53 -0500 Subject: [PATCH 1/6] chore(examples): update examples readme --- examples/README.md | 114 +++++++++++++++++++---------- examples/fixtures/example.sdl.yaml | 2 +- 2 files changed, 76 insertions(+), 40 deletions(-) diff --git a/examples/README.md b/examples/README.md index 046dc7b..0998f84 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,7 +2,50 @@ This directory contains several examples of how to interact with the Akash networking using AkashJS and CosmJS. -## Wallet Creation +You can integrate the following examples in either a nodejs environment with self managed keys/wallet or in the browser using wallet extensions. + +- [How to setup for node.js](#how-to-setup-for-nodejs) +- [How to setup for browser](#how-to-setup-for-browser) + +Once you have a wallet setup, you need to fund it with $AKT tokens to pay for transactions fees and deployments. + +- [Purchasing Akash Tokens](https://akash.network/docs/getting-started/token-and-wallets/#purchasing-akash-tokens) + +Please follow the following examples to interact with the Akash Network: + +- [Create wallet](./create_wallet.ts) +- [Create deployment](./create_deployment.ts) +- [Close deployment](./take_down_deployment.ts) +- [Estimate gas](./estimate_gas.ts) +- [Send tokens](./signed_msg_send.ts) +- [Amino signing](./signed_message.ts) + +Querying examples: + +- [Get deployments](./get_deployments.ts) +- [Get lease status](./get_lease_status.ts) +- [Get providers](./list_all_providers.ts) +- [Get single provider](./details_of_single_provider.ts) +- [Get network state](./get_state.ts) + +## Running an example + +To run an example, you need to make the required changes to the code and use typescript compiler. You can use the following command to run the example. E.g. to run the `create_deployment.ts` example: + +```bash +cd examples +ts-node -r tsconfig-paths/register create_deployment.ts +``` + +## How to setup for nodejs + +First you need a wallet. You can either [create one](#wallet-creation) with programatically cosmjs libraries or the **recommended** way is through an existing browser wallet extension where you can save the seed phrase (mnemonic) somewhere secure and use it to import the wallet like [this example](https://github.com/akash-network/akashjs/blob/main/examples/create_deployment.ts#L44). + +## How to setup for browser + +We strongly recommend to use [cosmos-kit](https://docs.cosmology.zone/cosmos-kit/get-started), which supports multiple wallet extensions and a lot of utility functions. Follow their get started guide to setup your React application to be able to interact with a wallet extension and broadcast transactions. + +### Wallet Creation The following code shows an example of the process for creating a new Akash wallet. The wallet can be used to access accounts which contain private/public key pairs and their associated addresses. @@ -66,40 +109,9 @@ const signedMessage = await wallet.signAmino( ); ``` -## Validating An Address - -Currently neither `cosmjs` or `akashjs` have methods of directly validating addresses. A basic validation can be done in JavaScript using either a RegEx or by attempting to convert the address to a public key. - -## Unsigned Transactions - -Basic transactions that do not requiring signing (such as querying) can be done using the basic RPC capabilities build into `akashjs`. For example, to query the list of deployments, an RPC request can be created as such. - -```ts -import { - QueryDeploymentsResponse, - QueryDeploymentsRequest, - QueryClientImpl -} from "@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta1/query"; -import { getRpc } from "@akashnetwork/akashjs/build/rpc" - -const request = QueryDeploymentsRequest.fromJSON({ - filters: { - owner: "akashSomeOwnerAddress", - } -}); -``` - -Once the request has been created, it can be passed to the appropriate ClientImpl method (`Deployments` in this case). - -```ts -const client = new QueryClientImpl(await getRpc("http://your.rpc.node")); -const response = await client.Deployments(request); -const data = QueryDeploymentsResponse.toJSON(response); -``` - ## Signed Transactions -For transactions that requiring signing, requests must be passed through the signing client. [AkashJS](https://github.com/ovrclk/akashjs) provides cosmjs compatible implementations of the Akash message types. +For transactions that require signing, requests must be passed through the signing client. [AkashJS](https://github.com/ovrclk/akashjs) provides cosmjs compatible implementations of the Akash message types. To create the message, the appropriate _type_ can be imported from `akashjs`. @@ -111,6 +123,7 @@ import { MsgCloseDeployment } from "@akashnetwork/akashjs/build/src/protobuf/aka This type contains the methods needed to construct a message that can then be passed into a Stargate client to be signed and broadcast. ```ts +// Import your wallet using your seed phrase/mnemonic const mnemonic = "your wallet mnemonic"; const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "akash" }); @@ -132,13 +145,14 @@ const msgAny = { }; // You can use your own RPC node, or get a list of public nodes from akashjs -const rpcEndpoint = "http://my.rpc.node"; +const rpcEndpoint = "http://rpc.akashnet.net"; // The akash types need to be registered with the client const myRegistry = new Registry( getAkashTypeRegistry() ); +// Instantiate the signer client const client = await SigningStargateClient.connectWithSigner( rpcEndpoint, wallet, @@ -147,6 +161,7 @@ const client = await SigningStargateClient.connectWithSigner( } ); +// Create the fee object to pay for the transaction const fee = { amount: [ { @@ -157,6 +172,7 @@ const fee = { gas: "800000", }; +// Sign and broadcast the transaction const signedMessage = await client.signAndBroadcast( account.address, [msgAny], @@ -275,11 +291,31 @@ When sending transactions, it can be useful to get an estimate of the gas requir console.log(gas); ``` -## Running an example +## Querying on chain data -To run an example, you need to make the required changes to the code and use typescript compiler. You can use the following command to run the example. E.g. to run the `create_deployment.ts` example: +Querying on-chain data can be done using the basic RPC capabilities build into `akashjs`. -```bash -cd examples -ts-node -r tsconfig-paths/register create_deployment.ts +For example, to query the list of deployments, an RPC request can be created as such. + +```ts +import { + QueryDeploymentsResponse, + QueryDeploymentsRequest, + QueryClientImpl +} from "@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta1/query"; +import { getRpc } from "@akashnetwork/akashjs/build/rpc" + +const request = QueryDeploymentsRequest.fromJSON({ + filters: { + owner: "akashSomeOwnerAddress", + } +}); +``` + +Once the request has been created, it can be passed to the appropriate ClientImpl method (`Deployments` in this case). + +```ts +const client = new QueryClientImpl(await getRpc("https://rpc.akashnet.net")); // This can also be your own custom rpc node +const response = await client.Deployments(request); +const data = QueryDeploymentsResponse.toJSON(response); ``` \ No newline at end of file diff --git a/examples/fixtures/example.sdl.yaml b/examples/fixtures/example.sdl.yaml index b322f8e..8130147 100644 --- a/examples/fixtures/example.sdl.yaml +++ b/examples/fixtures/example.sdl.yaml @@ -14,7 +14,7 @@ services: # The name of the service "web" web: # The docker container image with version. You must specify a version, the "latest" tag doesn't work. - image: akashlytics/hello-akash-world:0.2.0 + image: baktun/hello-akash-world:1.0.0 # You can map ports here https://akash.network/docs/getting-started/stack-definition-language/#servicesexpose expose: - port: 3000 From b10f6f1ec2955ed320c33480fd9fcb27d0b31da1 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:47:54 -0500 Subject: [PATCH 2/6] chore(examples): improve docs + command to launch example --- examples/README.md | 3 +-- examples/create_deployment.ts | 8 +++++++- package.json | 3 ++- src/certificates/generate509/generate509.ts | 13 +++---------- tests/test_deployments.ts | 5 ++--- 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/examples/README.md b/examples/README.md index 0998f84..c5ef943 100644 --- a/examples/README.md +++ b/examples/README.md @@ -33,8 +33,7 @@ Querying examples: To run an example, you need to make the required changes to the code and use typescript compiler. You can use the following command to run the example. E.g. to run the `create_deployment.ts` example: ```bash -cd examples -ts-node -r tsconfig-paths/register create_deployment.ts +npm run example create_deployment.ts ``` ## How to setup for nodejs diff --git a/examples/create_deployment.ts b/examples/create_deployment.ts index c0070e2..eaf37b0 100644 --- a/examples/create_deployment.ts +++ b/examples/create_deployment.ts @@ -13,10 +13,16 @@ import { CertificatePem } from "@akashnetwork/akashjs/build/certificates/certifi import { certificateManager } from "@akashnetwork/akashjs/build/certificates/certificate-manager"; import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; -// update this with your wallet mnemonic +// In case you want to test on a sandbox environment, uncomment the following line and comment the following line const rpcEndpoint = "https://rpc.sandbox-01.aksh.pw"; + +// Update this with your RPC endpoint // const rpcEndpoint = "https://rpc.akashnet.net:443"; + +// Update this with your wallet mnemonic const mnemonic = fs.readFileSync(path.resolve(__dirname, "./fixtures/mnemonic.txt"), "utf8").trim(); + +// Update this with your SDL file const rawSDL = fs.readFileSync(path.resolve(__dirname, "./fixtures/example.sdl.yaml"), "utf8"); const certificatePath = path.resolve(__dirname, "./fixtures/cert.json"); diff --git a/package.json b/package.json index c4b0157..6da5284 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "test:unit": "tap --ts tests/test_*.ts && jest --selectProjects unit", "test:unit-snapshot": "tap --ts --snapshot tests/test_*.ts", "test:unit:cov": "jest --selectProjects unit --coverage", - "test:unit:watch": "jest --selectProjects unit --watch" + "test:unit:watch": "jest --selectProjects unit --watch", + "example": "cd examples && npm i && ts-node -r tsconfig-paths/register" }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/src/certificates/generate509/generate509.ts b/src/certificates/generate509/generate509.ts index 3db1393..46fa110 100644 --- a/src/certificates/generate509/generate509.ts +++ b/src/certificates/generate509/generate509.ts @@ -16,6 +16,7 @@ const { BasicConstraints, Extension, ExtKeyUsage + // eslint-disable-next-line @typescript-eslint/no-var-requires } = require("pkijs/build"); const HASH_ALG = "SHA-256"; @@ -99,11 +100,7 @@ export async function create(address: string): Promise { * console.log(cert); // Outputs the generated certificate * }); */ -async function createCSR( - keyPair: { privateKey: string; publicKey: string }, - hashAlg: string, - { commonName }: { commonName: string } -) { +async function createCSR(keyPair: { privateKey: string; publicKey: string }, hashAlg: string, { commonName }: { commonName: string }) { const cert = new Certificate(); cert.version = 2; // Set certificate version to v3 @@ -220,11 +217,7 @@ function formatPEM(pemString: string) { * console.log(cert.notBefore.value); // Outputs the start date * console.log(cert.notAfter.value); // Outputs the end date */ -function setValidityPeriod( - cert: { notBefore: { value: Date }; notAfter: { value: Date } }, - startDate: Date, - durationInDays: number -) { +function setValidityPeriod(cert: { notBefore: { value: Date }; notAfter: { value: Date } }, startDate: Date, durationInDays: number) { // Normalize start date to midnight const start = new Date(startDate); start.setHours(0); diff --git a/tests/test_deployments.ts b/tests/test_deployments.ts index f6f8179..4de19a8 100644 --- a/tests/test_deployments.ts +++ b/tests/test_deployments.ts @@ -14,7 +14,7 @@ import { tap.test("Deployments: query deployment list with owner filter", async t => { t.plan(1); - const client = new QueryClientImpl(await getRpc("https://rpc.akash.forbole.com:443")); + const client = new QueryClientImpl(await getRpc("https://rpc.akashnet.net")); const request = QueryDeploymentsRequest.fromJSON({ filters: { owner: "akash1qqzwc5d7hynl67nsmn9jukvwqp3vzdl6j2t7lk" @@ -28,8 +28,7 @@ tap.test("Deployments: query deployment list with owner filter", async t => { tap.test("Deployments: query deployment with owner and dseq", async t => { t.plan(1); - const rpc = await getRpc("https://rpc.akash.forbole.com:443"); - const client = new QueryClientImpl(rpc); + const client = new QueryClientImpl(await getRpc("https://rpc.akashnet.net")); const request = QueryDeploymentRequest.fromJSON({ id: { owner: "akash1qqzwc5d7hynl67nsmn9jukvwqp3vzdl6j2t7lk", From 80f4bb3c9262b2e462e8f3761e38b7398fdf81a8 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:48:49 -0500 Subject: [PATCH 3/6] chore: lint fix --- examples/get_state.ts | 16 ++++----- src/certificates/certificate-manager/index.ts | 31 ++++++++--------- src/pbclient/pbclient.ts | 4 +-- src/sdl/SDL/SDL.ts | 34 +++++++++---------- src/sdl/index.ts | 6 ++-- src/types/global.d.ts | 4 +-- 6 files changed, 47 insertions(+), 48 deletions(-) diff --git a/examples/get_state.ts b/examples/get_state.ts index 7e7a4d1..8354671 100644 --- a/examples/get_state.ts +++ b/examples/get_state.ts @@ -1,13 +1,13 @@ import { getMetadata } from "@akashnetwork/akashjs/build/network"; async function fetchMetadata() { - try { - console.log("Fetching metadata..."); - const metadata = await getMetadata("mainnet"); - console.log(JSON.stringify(metadata, null, 2)); - } catch (error) { - console.error("Error fetching metadata:", error); - } + try { + console.log("Fetching metadata..."); + const metadata = await getMetadata("mainnet"); + console.log(JSON.stringify(metadata, null, 2)); + } catch (error) { + console.error("Error fetching metadata:", error); + } } -fetchMetadata(); \ No newline at end of file +fetchMetadata(); diff --git a/src/certificates/certificate-manager/index.ts b/src/certificates/certificate-manager/index.ts index 15f3d90..a134eb1 100644 --- a/src/certificates/certificate-manager/index.ts +++ b/src/certificates/certificate-manager/index.ts @@ -2,28 +2,27 @@ import { CertificateManager } from "./CertificateManager"; /** * An instance of the CertificateManager class. - * + * * @example * // Import the certificateManager instance * import { certificateManager } from './certificate-manager'; - * + * * // Use the certificateManager instance * certificateManager.issueCertificate('user123'); */ const certificateManager = new CertificateManager(); -export { - /** - * CertificateManager class for managing certificates. - * - * @example - * // Import the CertificateManager class - * import { CertificateManager } from './certificate-manager'; - * - * // Create a new instance of CertificateManager - * const myCertificateManager = new CertificateManager(); - */ - CertificateManager, - - certificateManager +export { + /** + * CertificateManager class for managing certificates. + * + * @example + * // Import the CertificateManager class + * import { CertificateManager } from './certificate-manager'; + * + * // Create a new instance of CertificateManager + * const myCertificateManager = new CertificateManager(); + */ + CertificateManager, + certificateManager }; diff --git a/src/pbclient/pbclient.ts b/src/pbclient/pbclient.ts index 869d047..86297c6 100644 --- a/src/pbclient/pbclient.ts +++ b/src/pbclient/pbclient.ts @@ -14,7 +14,7 @@ const FEE = { /** * Creates an Amino message object. - * + * * @param {Message} message - The type of the message. * @param {AminoMsg} messageBody - The body of the message. * @returns {object} The Amino message object. @@ -36,7 +36,7 @@ type MessageTypes = { /** * Creates a Stargate message object with a fee. - * + * * @template T * @param {T} message - The type of the message. * @param {MessageTypes[T]} messageBody - The body of the message. diff --git a/src/sdl/SDL/SDL.ts b/src/sdl/SDL/SDL.ts index b985ed4..47150a5 100644 --- a/src/sdl/SDL/SDL.ts +++ b/src/sdl/SDL/SDL.ts @@ -60,11 +60,11 @@ type NetworkVersion = "beta2" | "beta3"; /** * SDL (Stack Definition Language) parser and validator * Handles parsing and validation of Akash deployment manifests - * + * * @example * ```ts * import { SDL } from './SDL'; - * + * * const yaml = ` * version: "2.0" * services: @@ -76,13 +76,13 @@ type NetworkVersion = "beta2" | "beta3"; * to: * - global: true * `; - * + * * // Parse SDL from YAML string * const sdl = SDL.fromString(yaml); - * + * * // Get deployment manifest * const manifest = sdl.manifest(); - * + * * // Get deployment groups * const groups = sdl.groups(); * ``` @@ -90,12 +90,12 @@ type NetworkVersion = "beta2" | "beta3"; export class SDL { /** * Creates an SDL instance from a YAML string. - * + * * @param {string} yaml - The YAML string containing the SDL definition. * @param {NetworkVersion} [version="beta2"] - The SDL version (beta2 or beta3). * @param {NetworkId} [networkId=MAINNET_ID] - The network ID to validate against. * @returns {SDL} An instance of the SDL class. - * + * * @example * ```ts * const yaml = ` @@ -135,11 +135,11 @@ export class SDL { /** * Validates the GPU configuration for a given service profile. - * + * * @param {string} name - The name of the service profile. * @param {v3ResourceGPU | undefined} gpu - The GPU resource configuration. * @throws Will throw an error if the GPU configuration is invalid. - * + * * @example * ```ts * const gpuConfig = { units: "1", attributes: { vendor: { nvidia: [{ model: "RTX 3080", ram: "10GB" }] } } }; @@ -189,11 +189,11 @@ export class SDL { /** * Validates the storage configuration for a given service. - * + * * @param {string} name - The name of the service. * @param {v2ResourceStorage | v2ResourceStorageArray} [storage] - The storage resource configuration. * @throws Will throw an error if the storage configuration is invalid. - * + * * @example * ```ts * const storageConfig = { size: "10Gi", attributes: { class: "ssd" } }; @@ -663,11 +663,11 @@ export class SDL { /** * Parses the service protocol. - * + * * @param proto - The protocol string (e.g., "TCP", "UDP"). * @returns The parsed protocol. * @throws Will throw an error if the protocol is unsupported. - * + * * @example * ```ts * const protocol = SDL.parseServiceProto("TCP"); @@ -911,10 +911,10 @@ export class SDL { /** * Computes the endpoint sequence numbers for the given SDL. - * + * * @param sdl - The SDL data. * @returns An object mapping IPs to their sequence numbers. - * + * * @example * ```ts * const sequenceNumbers = sdl.computeEndpointSequenceNumbers(sdlData); @@ -1240,10 +1240,10 @@ export class SDL { /** * Escapes HTML characters in a string. - * + * * @param raw - The raw string to escape. * @returns The escaped string. - * + * * @example * ```ts * const escaped = sdl.escapeHtml("
Hello
"); diff --git a/src/sdl/index.ts b/src/sdl/index.ts index 66684ca..1835964 100644 --- a/src/sdl/index.ts +++ b/src/sdl/index.ts @@ -1,11 +1,11 @@ /** * SDL (Stack Definition Language) module exports * Provides functionality for parsing and validating Akash deployment manifests - * + * * @example * ```ts * import { SDL } from './sdl'; - * + * * const yaml = ` * version: "2.0" * services: @@ -17,7 +17,7 @@ * to: * - global: true * `; - * + * * const sdl = SDL.fromString(yaml); * const manifest = sdl.manifest(); * ``` diff --git a/src/types/global.d.ts b/src/types/global.d.ts index 177b0da..f74f1d3 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -11,11 +11,11 @@ declare global { /** * Gets an offline signer for automatic signing of transactions. * This method is injected by the Keplr wallet extension. - * + * * @param {string} chainId - The chain ID of the network to connect to * @returns {Promise} A promise that resolves to either an OfflineSigner or OfflineDirectSigner * @throws {Error} If Keplr wallet is not installed or user rejects the connection - * + * * @example * const signer = await window.getOfflineSignerAuto("cosmoshub-4"); */ From f769298ef735f9587bf92534e286297cbabdd3bc Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:27:34 -0500 Subject: [PATCH 4/6] chore(examples): improve examples readme + add dotenv for mnemonic loading --- .env.sample | 1 + .gitignore | 1 + examples/README.md | 233 ++++++++++++------------- examples/create_deployment.ts | 14 +- examples/details_of_single_provider.ts | 2 +- examples/estimate_gas.ts | 10 +- examples/fixtures/cert.json | 1 + examples/fixtures/mnemonic.txt | 1 - examples/get_deployments.ts | 2 +- examples/get_lease_status.ts | 2 +- examples/list_all_providers.ts | 2 +- examples/signed_msg_send.ts | 8 +- examples/take_down_deployment.ts | 12 +- package-lock.json | 13 ++ package.json | 5 +- 15 files changed, 166 insertions(+), 141 deletions(-) create mode 100644 .env.sample create mode 100644 examples/fixtures/cert.json delete mode 100644 examples/fixtures/mnemonic.txt diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..315944e --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +MNEMONIC='' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 90deaa0..f2a8915 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ yarn-error.log .coverage/ coverage/ +.env diff --git a/examples/README.md b/examples/README.md index c5ef943..727b4f1 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,6 +2,8 @@ This directory contains several examples of how to interact with the Akash networking using AkashJS and CosmJS. +## Setup + You can integrate the following examples in either a nodejs environment with self managed keys/wallet or in the browser using wallet extensions. - [How to setup for node.js](#how-to-setup-for-nodejs) @@ -11,6 +13,12 @@ Once you have a wallet setup, you need to fund it with $AKT tokens to pay for tr - [Purchasing Akash Tokens](https://akash.network/docs/getting-started/token-and-wallets/#purchasing-akash-tokens) +Environment variable setup: + +- Create an `.env` file at the root of the repo following the [.env.sample](../.env.sample) + +## Code examples + Please follow the following examples to interact with the Akash Network: - [Create wallet](./create_wallet.ts) @@ -34,6 +42,8 @@ To run an example, you need to make the required changes to the code and use typ ```bash npm run example create_deployment.ts +// or other examples +npm run example ``` ## How to setup for nodejs @@ -44,7 +54,27 @@ First you need a wallet. You can either [create one](#wallet-creation) with prog We strongly recommend to use [cosmos-kit](https://docs.cosmology.zone/cosmos-kit/get-started), which supports multiple wallet extensions and a lot of utility functions. Follow their get started guide to setup your React application to be able to interact with a wallet extension and broadcast transactions. -### Wallet Creation +## Dependencies installation + +Install the following dependencies for general usage and signing transactions with an existing wallet + +```bash +npm i @akashnetwork/akash-api @akashnetwork/akashjs @cosmjs/stargate @cosmjs/proto-signing +``` + +If you want to create wallets + +```bash +npm i @cosmjs/launchpad +``` + +For [amino encoding](https://docs.cosmos.network/main/learn/advanced/encoding#encoding-1) + +```bash +npm i @cosmjs/amino +``` + +### Wallet Creation The following code shows an example of the process for creating a new Akash wallet. The wallet can be used to access accounts which contain private/public key pairs and their associated addresses. @@ -54,9 +84,7 @@ A new wallet can be initialized by calling `Secp256k1HdWallet.generate` from @co import { Secp256k1HdWallet } from "@cosmjs/launchpad"; // the first parameter for generate is the size of the mnemonic, default is 12 -const wallet = await Secp256k1HdWallet - .generate(undefined, { prefix: "akash" }); - +const wallet = await Secp256k1HdWallet.generate(undefined, { prefix: "akash" }); ``` After the wallet is created, specific private/public key pairs are available via `getAccounts`. @@ -64,8 +92,7 @@ After the wallet is created, specific private/public key pairs are available via ```ts import { Secp256k1HdWallet } from "@cosmjs/launchpad"; -const wallet = await Secp256k1HdWallet - .generate(undefined, { prefix: "akash" }); +const wallet = await Secp256k1HdWallet.generate(undefined, { prefix: "akash" }); // gets the first account const [account] = await wallet.getAccounts(); @@ -76,8 +103,7 @@ The account address, as well as its public key, are available as properties on t ```ts import { Secp256k1HdWallet } from "@cosmjs/launchpad"; -const wallet = await Secp256k1HdWallet - .generate(undefined, { prefix: "akash" }); +const wallet = await Secp256k1HdWallet.generate(undefined, { prefix: "akash" }); const [account] = await wallet.getAccounts(); @@ -96,16 +122,12 @@ function getMessage(): StdSignDoc { // implements custom message } -const wallet = await Secp256k1HdWallet - .generate(undefined, { prefix: "akash" }); +const wallet = await Secp256k1HdWallet.generate(undefined, { prefix: "akash" }); const [account] = await wallet.getAccounts(); const msg = getMessage(); // your custom message -const signedMessage = await wallet.signAmino( - account.address, - msg -); +const signedMessage = await wallet.signAmino(account.address, msg); ``` ## Signed Transactions @@ -116,7 +138,6 @@ To create the message, the appropriate _type_ can be imported from `akashjs`. ```ts import { MsgCloseDeployment } from "@akashnetwork/akashjs/build/src/protobuf/akash/deployment/v1beta1/deployment"; - ``` This type contains the methods needed to construct a message that can then be passed into a Stargate client to be signed and broadcast. @@ -131,53 +152,42 @@ const [account] = await wallet.getAccounts(); // Use the encode method for the message to wrap the data const message = MsgCloseDeployment.fromPartial({ - id: { - dseq: "555555", - owner: account.address, - } + id: { + dseq: "555555", + owner: account.address + } }); // Set the appropriate typeUrl and attach the encoded message as the value const msgAny = { - typeUrl: getTypeUrl(MsgCloseDeployment), - value: message + typeUrl: getTypeUrl(MsgCloseDeployment), + value: message }; // You can use your own RPC node, or get a list of public nodes from akashjs const rpcEndpoint = "http://rpc.akashnet.net"; // The akash types need to be registered with the client -const myRegistry = new Registry( - getAkashTypeRegistry() -); +const myRegistry = new Registry(getAkashTypeRegistry()); // Instantiate the signer client -const client = await SigningStargateClient.connectWithSigner( - rpcEndpoint, - wallet, - { - registry: myRegistry - } -); +const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet, { + registry: myRegistry +}); // Create the fee object to pay for the transaction const fee = { - amount: [ - { - denom: "uakt", - amount: "5000", - }, - ], - gas: "800000", + amount: [ + { + denom: "uakt", + amount: "5000" + } + ], + gas: "800000" }; // Sign and broadcast the transaction -const signedMessage = await client.signAndBroadcast( - account.address, - [msgAny], - fee, - "take down deployment" -); +const signedMessage = await client.signAndBroadcast(account.address, [msgAny], fee, "take down deployment"); ``` ## Signing MsgSend @@ -197,48 +207,37 @@ const [account] = await wallet.getAccounts(); // Setup a send message manually. See the appropriate repo (cosmjs in this case) // for the specific shape of the message. const message = { - fromAddress: account.address, - toAddress: "akash123...", - amount: coins(10, "akt"), + fromAddress: account.address, + toAddress: "akash123...", + amount: coins(10, "akt") }; // Set the appropriate typeUrl and attach the encoded message as the value const msgAny = { - typeUrl: '/cosmos.bank.v1beta1.MsgSend', - value: message + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: message }; // You can use your own RPC node, or get a list of public nodes from akashjs const rpcEndpoint = "http://your.rpc.node"; -const myRegistry = new Registry( - defaultRegistryTypes -); +const myRegistry = new Registry(defaultRegistryTypes); -const client = await SigningStargateClient.connectWithSigner( - rpcEndpoint, - wallet, - { - registry: myRegistry - } -); +const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet, { + registry: myRegistry +}); const fee = { - amount: [ - { - denom: "uakt", - amount: "5000", - }, - ], - gas: "800000", + amount: [ + { + denom: "uakt", + amount: "5000" + } + ], + gas: "800000" }; -const msg = await client.sign( - account.address, - [msgAny], - fee, - "send some tokens" -); +const msg = await client.sign(account.address, [msgAny], fee, "send some tokens"); ``` ## Estimating Gas @@ -246,48 +245,38 @@ const msg = await client.sign( When sending transactions, it can be useful to get an estimate of the gas required for a given message. This can be done using the `simulate` method of the signing client which will send the passed in message to an RPC node which will return the estimated gas for that transaction. Before is an example of doing this for the same transaction as shown above. ```ts - const mnemonic = "your wallet mnemonic"; - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "akash" }); - - // get first account - const [account] = await wallet.getAccounts(); - - // Use the encode method for the message to wrap the data - const message = MsgCloseDeployment.fromPartial({ - id: { - dseq: "555555", - owner: account.address, - } - }); - - // Set the appropriate typeUrl and attach the encoded message as the value - const msgAny = { - typeUrl: getTypeUrl(MsgCloseDeployment), - value: message - }; - - // You can use your own RPC node, or get a list of public nodes from akashjs - const rpcEndpoint = "http://my.rpc.node"; - - const myRegistry = new Registry( - getAkashTypeRegistry() - ); - - const client = await SigningStargateClient.connectWithSigner( - rpcEndpoint, - wallet, - { - registry: myRegistry - } - ); - - const gas = await client.simulate( - account.address, - [msgAny], - "take down deployment" - ); - - console.log(gas); +const mnemonic = "your wallet mnemonic"; +const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "akash" }); + +// get first account +const [account] = await wallet.getAccounts(); + +// Use the encode method for the message to wrap the data +const message = MsgCloseDeployment.fromPartial({ + id: { + dseq: "555555", + owner: account.address + } +}); + +// Set the appropriate typeUrl and attach the encoded message as the value +const msgAny = { + typeUrl: getTypeUrl(MsgCloseDeployment), + value: message +}; + +// You can use your own RPC node, or get a list of public nodes from akashjs +const rpcEndpoint = "http://rpc.akashnet.net"; + +const myRegistry = new Registry(getAkashTypeRegistry()); + +const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet, { + registry: myRegistry +}); + +const gas = await client.simulate(account.address, [msgAny], "take down deployment"); + +console.log(gas); ``` ## Querying on chain data @@ -297,17 +286,13 @@ Querying on-chain data can be done using the basic RPC capabilities build into ` For example, to query the list of deployments, an RPC request can be created as such. ```ts -import { - QueryDeploymentsResponse, - QueryDeploymentsRequest, - QueryClientImpl -} from "@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta1/query"; -import { getRpc } from "@akashnetwork/akashjs/build/rpc" +import { QueryDeploymentsResponse, QueryDeploymentsRequest, QueryClientImpl } from "@akashnetwork/akashjs/build/protobuf/akash/deployment/v1beta1/query"; +import { getRpc } from "@akashnetwork/akashjs/build/rpc"; const request = QueryDeploymentsRequest.fromJSON({ - filters: { - owner: "akashSomeOwnerAddress", - } + filters: { + owner: "akashSomeOwnerAddress" + } }); ``` @@ -317,4 +302,4 @@ Once the request has been created, it can be passed to the appropriate const client = new QueryClientImpl(await getRpc("https://rpc.akashnet.net")); // This can also be your own custom rpc node const response = await client.Deployments(request); const data = QueryDeploymentsResponse.toJSON(response); -``` \ No newline at end of file +``` diff --git a/examples/create_deployment.ts b/examples/create_deployment.ts index eaf37b0..f675196 100644 --- a/examples/create_deployment.ts +++ b/examples/create_deployment.ts @@ -12,15 +12,21 @@ import { getAkashTypeRegistry } from "@akashnetwork/akashjs/build/stargate"; import { CertificatePem } from "@akashnetwork/akashjs/build/certificates/certificate-manager/CertificateManager"; import { certificateManager } from "@akashnetwork/akashjs/build/certificates/certificate-manager"; import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; +import dotenv from "dotenv"; + +dotenv.config({ path: "../.env" }); // In case you want to test on a sandbox environment, uncomment the following line and comment the following line -const rpcEndpoint = "https://rpc.sandbox-01.aksh.pw"; +// const rpcEndpoint = "https://rpc.sandbox-01.aksh.pw"; // Update this with your RPC endpoint -// const rpcEndpoint = "https://rpc.akashnet.net:443"; +const rpcEndpoint = "https://rpc.akashnet.net:443"; -// Update this with your wallet mnemonic -const mnemonic = fs.readFileSync(path.resolve(__dirname, "./fixtures/mnemonic.txt"), "utf8").trim(); +// Update this environment variable with your wallet mnemonic +const mnemonic = process.env.MNEMONIC || ""; +if (!mnemonic) { + throw new Error("MNEMONIC environment variable is not set. Please set the environment variable in the .env file. See .env.sample for more information."); +} // Update this with your SDL file const rawSDL = fs.readFileSync(path.resolve(__dirname, "./fixtures/example.sdl.yaml"), "utf8"); diff --git a/examples/details_of_single_provider.ts b/examples/details_of_single_provider.ts index 4b10c6a..6dd0e37 100644 --- a/examples/details_of_single_provider.ts +++ b/examples/details_of_single_provider.ts @@ -2,7 +2,7 @@ import { QueryClientImpl, QueryProviderRequest, QueryProviderResponse } from "@a import { getRpc } from "@akashnetwork/akashjs/build/rpc"; async function main() { - const client = new QueryClientImpl(await getRpc("http://your.rpc.node")); + const client = new QueryClientImpl(await getRpc("http://rpc.akashnet.net")); const getProviderInfoRequest = QueryProviderRequest.fromPartial({ owner: "akashSomeProviderAddress" diff --git a/examples/estimate_gas.ts b/examples/estimate_gas.ts index db822af..4bed640 100644 --- a/examples/estimate_gas.ts +++ b/examples/estimate_gas.ts @@ -2,9 +2,15 @@ import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { SigningStargateClient } from "@cosmjs/stargate"; import { MsgCloseDeployment } from "@akashnetwork/akash-api/akash/deployment/v1beta3"; import { getAkashTypeRegistry, getTypeUrl } from "@akashnetwork/akashjs/build/stargate"; +import dotenv from "dotenv"; + +dotenv.config({ path: "../.env" }); async function main() { - const mnemonic = "your wallet mnemonic"; + const mnemonic = process.env.MNEMONIC || ""; + if (!mnemonic) { + throw new Error("MNEMONIC environment variable is not set. Please set the environment variable in the .env file. See .env.sample for more information."); + } const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "akash" }); // get first account @@ -25,7 +31,7 @@ async function main() { }; // You can use your own RPC node, or get a list of public nodes from akashjs - const rpcEndpoint = "http://my.rpc.node"; + const rpcEndpoint = "http://rpc.akashnet.net"; const myRegistry = new Registry(getAkashTypeRegistry()); diff --git a/examples/fixtures/cert.json b/examples/fixtures/cert.json new file mode 100644 index 0000000..7c10362 --- /dev/null +++ b/examples/fixtures/cert.json @@ -0,0 +1 @@ +{"cert":"-----BEGIN CERTIFICATE-----\r\nMIIBmzCCAUGgAwIBAgIHBivoizeG2DAKBggqhkjOPQQDAjA3MTUwMwYDVQQDDCxh\r\na2FzaDFwMmU3M3ZwaHk5dW1zeDAyeTZ4cXI0OXlldTBkbjlzM3B5dGt2azAeFw0y\r\nNTAxMTcxNTI3MDhaFw0yNjAxMTcxNTI3MDhaMDcxNTAzBgNVBAMMLGFrYXNoMXAy\r\nZTczdnBoeTl1bXN4MDJ5NnhxcjQ5eWV1MGRuOXMzcHl0a3ZrMFkwEwYHKoZIzj0C\r\nAQYIKoZIzj0DAQcDQgAEzMqJ29jDG6EZGDLL1g9TX1kjE4qGPWhkGfa9AUMQFsdY\r\njVHwV8bZI1UxfZLWrkhRMZ5+23iHmV6OL01Df8yUlqM4MDYwDgYDVR0PAQH/BAQD\r\nAgQwMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI\r\nzj0EAwIDSAAwRQIgX8ewEpsdIqIWveH4wemnGUSvts8jIh89NtcZV2gQHWECIQCX\r\ncNF7U+0wZaJ+7PQmUCEoZ/4dsMCL4LiZmspr0akChg==\r\n-----END CERTIFICATE-----\r\n","publicKey":"-----BEGIN EC PUBLIC KEY-----\r\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzMqJ29jDG6EZGDLL1g9TX1kjE4qG\r\nPWhkGfa9AUMQFsdYjVHwV8bZI1UxfZLWrkhRMZ5+23iHmV6OL01Df8yUlg==\r\n-----END EC PUBLIC KEY-----\r\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfUA//ul4AYxdOeCX\r\nsI4vEb14P3c/jnNboJNFsaVrVIehRANCAATMyonb2MMboRkYMsvWD1NfWSMTioY9\r\naGQZ9r0BQxAWx1iNUfBXxtkjVTF9ktauSFExnn7beIeZXo4vTUN/zJSW\r\n-----END PRIVATE KEY-----\r\n"} \ No newline at end of file diff --git a/examples/fixtures/mnemonic.txt b/examples/fixtures/mnemonic.txt deleted file mode 100644 index 570356c..0000000 --- a/examples/fixtures/mnemonic.txt +++ /dev/null @@ -1 +0,0 @@ -your mnemonic here \ No newline at end of file diff --git a/examples/get_deployments.ts b/examples/get_deployments.ts index 50f1a2c..0bc208d 100644 --- a/examples/get_deployments.ts +++ b/examples/get_deployments.ts @@ -8,7 +8,7 @@ async function main() { } }); - const client = new QueryClientImpl(await getRpc("http://your.rpc.node")); + const client = new QueryClientImpl(await getRpc("http://rpc.akashnet.net")); const response = await client.Deployments(request); const data = QueryDeploymentsResponse.toJSON(response); diff --git a/examples/get_lease_status.ts b/examples/get_lease_status.ts index 119b231..848a603 100644 --- a/examples/get_lease_status.ts +++ b/examples/get_lease_status.ts @@ -2,7 +2,7 @@ import { QueryClientImpl, QueryLeaseRequest, QueryLeaseResponse } from "@akashne import { getRpc } from "@akashnetwork/akashjs/build/rpc"; async function main() { - const client = new QueryClientImpl(await getRpc("http://your.rpc.node")); + const client = new QueryClientImpl(await getRpc("http://rpc.akashnet.net")); const getLeaseStatusRequest = QueryLeaseRequest.fromPartial({ id: { diff --git a/examples/list_all_providers.ts b/examples/list_all_providers.ts index 9ea1d1f..02a24ce 100644 --- a/examples/list_all_providers.ts +++ b/examples/list_all_providers.ts @@ -2,7 +2,7 @@ import { QueryClientImpl, QueryProvidersRequest, QueryProvidersResponse } from " import { getRpc } from "@akashnetwork/akashjs/build/rpc"; async function main() { - const client = new QueryClientImpl(await getRpc("http://your.rpc.node")); + const client = new QueryClientImpl(await getRpc("http://rpc.akashnet.net")); const providersRequest = QueryProvidersRequest.fromPartial({ pagination: { diff --git a/examples/signed_msg_send.ts b/examples/signed_msg_send.ts index 6e592e2..b8fe88e 100644 --- a/examples/signed_msg_send.ts +++ b/examples/signed_msg_send.ts @@ -1,8 +1,14 @@ import { coins, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { defaultRegistryTypes, SigningStargateClient } from "@cosmjs/stargate"; +import dotenv from "dotenv"; + +dotenv.config({ path: "../.env" }); async function main() { - const mnemonic = "your wallet mnemonic"; + const mnemonic = process.env.MNEMONIC || ""; + if (!mnemonic) { + throw new Error("MNEMONIC environment variable is not set. Please set the environment variable in the .env file. See .env.sample for more information."); + } const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "akash" }); // get first account diff --git a/examples/take_down_deployment.ts b/examples/take_down_deployment.ts index fad6c31..7803120 100644 --- a/examples/take_down_deployment.ts +++ b/examples/take_down_deployment.ts @@ -2,9 +2,15 @@ import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { SigningStargateClient } from "@cosmjs/stargate"; import { getAkashTypeRegistry, getTypeUrl } from "@akashnetwork/akashjs/build/stargate"; import { MsgCloseDeployment } from "@akashnetwork/akash-api/akash/deployment/v1beta3"; +import dotenv from "dotenv"; + +dotenv.config({ path: "../.env" }); async function main() { - const mnemonic = "your wallet mnemonic"; + const mnemonic = process.env.MNEMONIC || ""; + if (!mnemonic) { + throw new Error("MNEMONIC environment variable is not set. Please set the environment variable in the .env file. See .env.sample for more information."); + } const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "akash" }); // get first account @@ -13,7 +19,7 @@ async function main() { // Use the encode method for the message to wrap the data const message = MsgCloseDeployment.fromPartial({ id: { - dseq: "555555", + dseq: "19837048", owner: account.address } }); @@ -25,7 +31,7 @@ async function main() { }; // You can use your own RPC node, or get a list of public nodes from akashjs - const rpcEndpoint = "http://my.rpc.node"; + const rpcEndpoint = "http://rpc.akashnet.net"; const myRegistry = new Registry(getAkashTypeRegistry()); diff --git a/package-lock.json b/package-lock.json index 9237f30..fbcdb4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "atob": "^2.1.2", "axios": "^0.24.0", "console-browserify": "^1.2.0", + "dotenv": "^16.4.7", "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", "jsrsasign": "^11.1.0", @@ -6320,6 +6321,18 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/duplexer2": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", diff --git a/package.json b/package.json index 6da5284..0f65ebd 100644 --- a/package.json +++ b/package.json @@ -57,17 +57,18 @@ "atob": "^2.1.2", "axios": "^0.24.0", "console-browserify": "^1.2.0", + "dotenv": "^16.4.7", "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", "jsrsasign": "^11.1.0", "keytar": "^7.7.0", + "lodash": "^4.17.21", "node-fetch": "2", "pkijs": "^3.0.0", "process": "^0.11.10", "pvutils": "^1.0.17", "simple-jsonrpc-js": "^1.2.0", - "sort-json": "^2.0.1", - "lodash": "^4.17.21" + "sort-json": "^2.0.1" }, "devDependencies": { "@commitlint/cli": "^19.2.2", From 960b90df4d8cf311e273ba9124741ec88f785a87 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:29:36 -0500 Subject: [PATCH 5/6] chore: remove cert.json --- examples/fixtures/cert.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 examples/fixtures/cert.json diff --git a/examples/fixtures/cert.json b/examples/fixtures/cert.json deleted file mode 100644 index 7c10362..0000000 --- a/examples/fixtures/cert.json +++ /dev/null @@ -1 +0,0 @@ -{"cert":"-----BEGIN CERTIFICATE-----\r\nMIIBmzCCAUGgAwIBAgIHBivoizeG2DAKBggqhkjOPQQDAjA3MTUwMwYDVQQDDCxh\r\na2FzaDFwMmU3M3ZwaHk5dW1zeDAyeTZ4cXI0OXlldTBkbjlzM3B5dGt2azAeFw0y\r\nNTAxMTcxNTI3MDhaFw0yNjAxMTcxNTI3MDhaMDcxNTAzBgNVBAMMLGFrYXNoMXAy\r\nZTczdnBoeTl1bXN4MDJ5NnhxcjQ5eWV1MGRuOXMzcHl0a3ZrMFkwEwYHKoZIzj0C\r\nAQYIKoZIzj0DAQcDQgAEzMqJ29jDG6EZGDLL1g9TX1kjE4qGPWhkGfa9AUMQFsdY\r\njVHwV8bZI1UxfZLWrkhRMZ5+23iHmV6OL01Df8yUlqM4MDYwDgYDVR0PAQH/BAQD\r\nAgQwMBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZI\r\nzj0EAwIDSAAwRQIgX8ewEpsdIqIWveH4wemnGUSvts8jIh89NtcZV2gQHWECIQCX\r\ncNF7U+0wZaJ+7PQmUCEoZ/4dsMCL4LiZmspr0akChg==\r\n-----END CERTIFICATE-----\r\n","publicKey":"-----BEGIN EC PUBLIC KEY-----\r\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzMqJ29jDG6EZGDLL1g9TX1kjE4qG\r\nPWhkGfa9AUMQFsdYjVHwV8bZI1UxfZLWrkhRMZ5+23iHmV6OL01Df8yUlg==\r\n-----END EC PUBLIC KEY-----\r\n","privateKey":"-----BEGIN PRIVATE KEY-----\r\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfUA//ul4AYxdOeCX\r\nsI4vEb14P3c/jnNboJNFsaVrVIehRANCAATMyonb2MMboRkYMsvWD1NfWSMTioY9\r\naGQZ9r0BQxAWx1iNUfBXxtkjVTF9ktauSFExnn7beIeZXo4vTUN/zJSW\r\n-----END PRIVATE KEY-----\r\n"} \ No newline at end of file From 659bd17738d89e89a65450072452b5f243eed762 Mon Sep 17 00:00:00 2001 From: Maxime Beauchamp <15185355+baktun14@users.noreply.github.com> Date: Fri, 17 Jan 2025 11:34:34 -0500 Subject: [PATCH 6/6] chore: ignore cert.json --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f2a8915..324a7a2 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ yarn-error.log coverage/ .env +cert.json