From 6c742b6c5f1f99ed743ae531e4d5cbaec57b229c Mon Sep 17 00:00:00 2001 From: Jordan Last Date: Wed, 17 Apr 2024 14:25:26 -0500 Subject: [PATCH] add authentication and limitations chapters --- docs/404.html | 2 +- docs/assets.html | 6 +- docs/authentication.html | 333 ++++++++++++++++++ docs/azle.html | 2 +- docs/candid.html | 2 +- docs/candid_based_documentation.html | 2 +- docs/canister_lifecycle.html | 2 +- docs/canisters_overview.html | 2 +- docs/caveats.html | 2 +- docs/cross_canister.html | 2 +- docs/cycles.html | 2 +- docs/debugging.html | 10 +- docs/deployment.html | 2 +- docs/deployment_candid_based.html | 2 +- docs/examples.html | 2 +- docs/get_started.html | 2 +- docs/hello_world.html | 2 +- docs/http.html | 2 +- docs/index.html | 2 +- docs/installation.html | 2 +- docs/internet_computer_overview.html | 2 +- docs/limitations.html | 226 ++++++++++++ docs/management_canister.html | 2 +- docs/print.html | 165 ++++++++- docs/project_structure.html | 2 +- docs/query_methods.html | 2 +- docs/reference/bitcoin.html | 2 +- docs/reference/call_apis/accept_message.html | 2 +- docs/reference/call_apis/arg_data_raw.html | 2 +- .../call_apis/arg_data_raw_size.html | 2 +- docs/reference/call_apis/call.html | 2 +- docs/reference/call_apis/call_apis.html | 2 +- docs/reference/call_apis/call_raw.html | 2 +- docs/reference/call_apis/call_raw128.html | 2 +- .../call_apis/call_with_payment.html | 2 +- .../call_apis/call_with_payment128.html | 2 +- docs/reference/call_apis/caller.html | 2 +- docs/reference/call_apis/method_name.html | 2 +- .../call_apis/msg_cycles_accept.html | 2 +- .../call_apis/msg_cycles_accept128.html | 2 +- .../call_apis/msg_cycles_available.html | 2 +- .../call_apis/msg_cycles_available128.html | 2 +- .../call_apis/msg_cycles_refunded.html | 2 +- .../call_apis/msg_cycles_refunded128.html | 2 +- docs/reference/call_apis/notify.html | 2 +- docs/reference/call_apis/notify_raw.html | 2 +- .../call_apis/notify_with_payment_128.html | 2 +- docs/reference/call_apis/reject.html | 2 +- docs/reference/call_apis/reject_code.html | 2 +- docs/reference/call_apis/reject_message.html | 2 +- docs/reference/call_apis/reply.html | 2 +- docs/reference/call_apis/reply_raw.html | 2 +- docs/reference/candid/blob.html | 2 +- docs/reference/candid/bool.html | 2 +- docs/reference/candid/candid.html | 2 +- docs/reference/candid/empty.html | 2 +- docs/reference/candid/float32.html | 2 +- docs/reference/candid/float64.html | 2 +- docs/reference/candid/func.html | 2 +- docs/reference/candid/int.html | 2 +- docs/reference/candid/int16.html | 2 +- docs/reference/candid/int32.html | 2 +- docs/reference/candid/int64.html | 2 +- docs/reference/candid/int8.html | 2 +- docs/reference/candid/nat.html | 2 +- docs/reference/candid/nat16.html | 2 +- docs/reference/candid/nat32.html | 2 +- docs/reference/candid/nat64.html | 2 +- docs/reference/candid/nat8.html | 2 +- docs/reference/candid/null.html | 2 +- docs/reference/candid/opt.html | 2 +- docs/reference/candid/principal.html | 2 +- docs/reference/candid/record.html | 2 +- docs/reference/candid/reserved.html | 2 +- docs/reference/candid/service.html | 2 +- docs/reference/candid/text.html | 2 +- docs/reference/candid/variant.html | 2 +- docs/reference/candid/vec.html | 2 +- .../canister_apis/candid_decode.html | 2 +- .../canister_apis/candid_encode.html | 2 +- .../canister_apis/canister_apis.html | 2 +- .../canister_apis/canister_balance.html | 2 +- .../canister_apis/canister_balance128.html | 2 +- docs/reference/canister_apis/canister_id.html | 2 +- .../canister_apis/canister_version.html | 2 +- .../canister_apis/data_certificate.html | 2 +- .../canister_apis/instruction_counter.html | 2 +- .../canister_apis/is_controller.html | 2 +- .../canister_apis/performance_counter.html | 2 +- docs/reference/canister_apis/print.html | 2 +- .../canister_apis/set_certified_data.html | 2 +- docs/reference/canister_apis/time.html | 2 +- docs/reference/canister_apis/trap.html | 2 +- .../canister_methods/canister_methods.html | 2 +- .../reference/canister_methods/heartbeat.html | 2 +- .../canister_methods/http_request.html | 2 +- .../canister_methods/http_request_update.html | 2 +- docs/reference/canister_methods/init.html | 2 +- .../canister_methods/inspect_message.html | 2 +- .../canister_methods/post_upgrade.html | 2 +- .../canister_methods/pre_upgrade.html | 2 +- docs/reference/canister_methods/query.html | 2 +- docs/reference/canister_methods/update.html | 2 +- docs/reference/environment_variables.html | 2 +- .../bitcoin_get_balance.html | 2 +- .../bitcoin_get_current_fee_percentiles.html | 2 +- .../bitcoin_get_utxos.html | 2 +- .../bitcoin_send_transaction.html | 2 +- .../management_canister/canister_status.html | 2 +- .../management_canister/create_canister.html | 2 +- .../management_canister/delete_canister.html | 2 +- .../management_canister/deposit_cycles.html | 2 +- .../management_canister/ecdsa_public_key.html | 2 +- .../management_canister/http_request.html | 2 +- .../management_canister/install_code.html | 2 +- .../management_canister.html | 2 +- ...ovisional_create_canister_with_cycles.html | 2 +- .../provisional_top_up_canister.html | 2 +- .../management_canister/raw_rand.html | 2 +- .../management_canister/sign_with_ecdsa.html | 2 +- .../management_canister/start_canister.html | 2 +- .../management_canister/stop_canister.html | 2 +- .../management_canister/uninstall_code.html | 2 +- .../management_canister/update_settings.html | 2 +- docs/reference/plugins.html | 2 +- docs/reference/reference.html | 2 +- .../stable_memory/stable64_grow.html | 2 +- .../stable_memory/stable64_read.html | 2 +- .../stable_memory/stable64_size.html | 2 +- .../stable_memory/stable64_write.html | 2 +- .../reference/stable_memory/stable_bytes.html | 2 +- docs/reference/stable_memory/stable_grow.html | 2 +- .../stable_memory/stable_memory.html | 2 +- docs/reference/stable_memory/stable_read.html | 2 +- docs/reference/stable_memory/stable_size.html | 2 +- .../stable_memory/stable_structures.html | 2 +- .../reference/stable_memory/stable_write.html | 2 +- docs/reference/timers/clear_timer.html | 2 +- docs/reference/timers/set_timer.html | 2 +- docs/reference/timers/set_timer_interval.html | 2 +- docs/reference/timers/timers.html | 2 +- docs/reference/wasm_binary_optimization.html | 2 +- docs/reference_http/autoreload.html | 2 +- .../reference_http/environment_variables.html | 2 +- docs/reference_http/native_compilation.html | 2 +- docs/reference_http/reference.html | 6 +- docs/rest_based_examples.html | 2 +- docs/searchindex.js | 2 +- docs/searchindex.json | 2 +- docs/servers.html | 2 +- docs/stable_structures.html | 2 +- docs/the_azle_book.html | 2 +- docs/timers.html | 2 +- docs/update_methods.html | 2 +- the_azle_book/book/404.html | 2 +- the_azle_book/book/azle.html | 2 +- the_azle_book/book/candid.html | 2 +- the_azle_book/book/canisters_overview.html | 2 +- the_azle_book/book/cross_canister.html | 2 +- the_azle_book/book/cycles.html | 2 +- the_azle_book/book/deployment.html | 2 +- the_azle_book/book/examples.html | 2 +- the_azle_book/book/http.html | 2 +- the_azle_book/book/index.html | 2 +- the_azle_book/book/installation.html | 2 +- .../book/internet_computer_overview.html | 2 +- the_azle_book/book/print.html | 165 ++++++++- the_azle_book/book/searchindex.js | 2 +- the_azle_book/book/searchindex.json | 2 +- the_azle_book/book/timers.html | 2 +- the_azle_book/src/SUMMARY.md | 6 +- the_azle_book/src/authentication.md | 154 ++++++++ the_azle_book/src/limitations.md | 28 +- 173 files changed, 1239 insertions(+), 186 deletions(-) create mode 100644 docs/authentication.html create mode 100644 docs/limitations.html create mode 100644 the_azle_book/src/authentication.md diff --git a/docs/404.html b/docs/404.html index c8cdbabd29..2c61492f49 100644 --- a/docs/404.html +++ b/docs/404.html @@ -84,7 +84,7 @@ diff --git a/docs/assets.html b/docs/assets.html index 6991a29acd..0bc055d841 100644 --- a/docs/assets.html +++ b/docs/assets.html @@ -83,7 +83,7 @@ @@ -195,7 +195,7 @@

Assets TL;DR

- @@ -209,7 +209,7 @@

Assets TL;DR

- diff --git a/docs/authentication.html b/docs/authentication.html new file mode 100644 index 0000000000..376638ad71 --- /dev/null +++ b/docs/authentication.html @@ -0,0 +1,333 @@ + + + + + + Authentication - The Azle Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Authentication TL;DR

+

Azle canisters can import ic from azle and use ic.caller() to get the principal (public-key linked identifier) of the initiator of an HTTP request. HTTP requests are anonymous (principal 2vxsx-fae) by default, but authentication with web browsers (and maybe Node.js) can be done using a JWT-like API from azle/http_client.

+

First you import toJwt from azle/http_client:

+
import { toJwt } from 'azle/http_client';
+
+

Then you use fetch and construct an Authorization header using an @dfinity/agent Identity:

+
const response = await fetch(
+    `http://bkyz2-fmaaa-aaaaa-qaaaq-cai.localhost:8000/whoami`,
+    {
+        method: 'GET',
+        headers: [['Authorization', toJwt(this.identity)]]
+    }
+);
+
+

Here's an example of the frontend of a simple web application using azle/http_client and Internet Identity:

+
import { Identity } from '@dfinity/agent';
+import { AuthClient } from '@dfinity/auth-client';
+import { toJwt } from 'azle/http_client';
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+@customElement('azle-app')
+export class AzleApp extends LitElement {
+    @property()
+    identity: Identity | null = null;
+
+    @property()
+    whoami: string = '';
+
+    connectedCallback() {
+        super.connectedCallback();
+        this.authenticate();
+    }
+
+    async authenticate() {
+        const authClient = await AuthClient.create();
+        const isAuthenticated = await authClient.isAuthenticated();
+
+        if (isAuthenticated === true) {
+            this.handleIsAuthenticated(authClient);
+        } else {
+            await this.handleIsNotAuthenticated(authClient);
+        }
+    }
+
+    handleIsAuthenticated(authClient: AuthClient) {
+        this.identity = authClient.getIdentity();
+    }
+
+    async handleIsNotAuthenticated(authClient: AuthClient) {
+        await new Promise((resolve, reject) => {
+            authClient.login({
+                identityProvider: import.meta.env.VITE_IDENTITY_PROVIDER,
+                onSuccess: resolve as () => void,
+                onError: reject,
+                windowOpenerFeatures: `width=500,height=500`
+            });
+        });
+
+        this.identity = authClient.getIdentity();
+    }
+
+    async whoamiUnauthenticated() {
+        const response = await fetch(
+            `${import.meta.env.VITE_CANISTER_ORIGIN}/whoami`
+        );
+        const responseText = await response.text();
+
+        this.whoami = responseText;
+    }
+
+    async whoamiAuthenticated() {
+        const response = await fetch(
+            `${import.meta.env.VITE_CANISTER_ORIGIN}/whoami`,
+            {
+                method: 'GET',
+                headers: [['Authorization', toJwt(this.identity)]]
+            }
+        );
+        const responseText = await response.text();
+
+        this.whoami = responseText;
+    }
+
+    render() {
+        return html`
+            <h1>Internet Identity</h1>
+
+            <h2>
+                Whoami principal:
+                <span id="whoamiPrincipal">${this.whoami}</span>
+            </h2>
+
+            <button
+                id="whoamiUnauthenticated"
+                @click=${this.whoamiUnauthenticated}
+            >
+                Whoami Unauthenticated
+            </button>
+            <button
+                id="whoamiAuthenticated"
+                @click=${this.whoamiAuthenticated}
+                .disabled=${this.identity === null}
+            >
+                Whoami Authenticated
+            </button>
+        `;
+    }
+}
+
+

Here's an example of the backend of that same simple web application:

+
import { ic } from 'azle';
+import express from 'express';
+
+const app = express();
+
+app.get('/whoami', (req, res) => {
+    res.send(ic.caller().toString());
+});
+
+app.use(express.static('/dist'));
+
+app.listen();
+
+

Authentication

+

Examples:

+ +

Under-the-hood

+

Authentication of ICP calls is done through signatures on messages. @dfinity/agent provides very nice abstractions for creating all of the required signatures in the correct formats when calling into canisters on ICP. Unfortunately this requires you to abandon traditional HTTP requests, as you must use the agent's APIs.

+

Azle attempts to enable you to perform traditional HTTP requests with traditional libraries. Currently Azle focuses on fetch. When importing toJwt, azle/http_client will overwrite the global fetch function and will intercept fetch requests that have Authorization headers with an Identity as a value.

+

Once intercepted, these requests are turned into @dfinity/agent requests that call the http_request and http_request_update canister methods directly, thus performing all of the required client-side authentication work.

+

We are working to push for ICP to more natively understand JWTs for authentication, without the need to intercept fetch requests and convert them into agent requests.

+ +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/azle.html b/docs/azle.html index d615730094..33a82021e8 100644 --- a/docs/azle.html +++ b/docs/azle.html @@ -83,7 +83,7 @@ diff --git a/docs/candid.html b/docs/candid.html index 8773252958..62a626b7ea 100644 --- a/docs/candid.html +++ b/docs/candid.html @@ -83,7 +83,7 @@ diff --git a/docs/candid_based_documentation.html b/docs/candid_based_documentation.html index bb415bb109..e49fd97ed5 100644 --- a/docs/candid_based_documentation.html +++ b/docs/candid_based_documentation.html @@ -83,7 +83,7 @@ diff --git a/docs/canister_lifecycle.html b/docs/canister_lifecycle.html index 77fd35f390..65e8f5c265 100644 --- a/docs/canister_lifecycle.html +++ b/docs/canister_lifecycle.html @@ -83,7 +83,7 @@ diff --git a/docs/canisters_overview.html b/docs/canisters_overview.html index cb900fa743..b034b5ec24 100644 --- a/docs/canisters_overview.html +++ b/docs/canisters_overview.html @@ -83,7 +83,7 @@ diff --git a/docs/caveats.html b/docs/caveats.html index 8c2768f530..502b136f47 100644 --- a/docs/caveats.html +++ b/docs/caveats.html @@ -83,7 +83,7 @@ diff --git a/docs/cross_canister.html b/docs/cross_canister.html index 9a7c074161..5edc092221 100644 --- a/docs/cross_canister.html +++ b/docs/cross_canister.html @@ -83,7 +83,7 @@ diff --git a/docs/cycles.html b/docs/cycles.html index bf1ba641be..ab5e489f30 100644 --- a/docs/cycles.html +++ b/docs/cycles.html @@ -83,7 +83,7 @@ diff --git a/docs/debugging.html b/docs/debugging.html index 0d4f483876..80f8c6e8f3 100644 --- a/docs/debugging.html +++ b/docs/debugging.html @@ -83,7 +83,7 @@ @@ -223,11 +223,11 @@

- - @@ -237,11 +237,11 @@

- - diff --git a/docs/deployment.html b/docs/deployment.html index 00304d208a..50bbdac97b 100644 --- a/docs/deployment.html +++ b/docs/deployment.html @@ -83,7 +83,7 @@ diff --git a/docs/deployment_candid_based.html b/docs/deployment_candid_based.html index 91c182a3f9..c6c82ef52e 100644 --- a/docs/deployment_candid_based.html +++ b/docs/deployment_candid_based.html @@ -83,7 +83,7 @@ diff --git a/docs/examples.html b/docs/examples.html index b5bd9f0f16..df2e17d73b 100644 --- a/docs/examples.html +++ b/docs/examples.html @@ -83,7 +83,7 @@ diff --git a/docs/get_started.html b/docs/get_started.html index b17e7965b9..eba6154173 100644 --- a/docs/get_started.html +++ b/docs/get_started.html @@ -83,7 +83,7 @@ diff --git a/docs/hello_world.html b/docs/hello_world.html index 420a6941be..e7a5e55c87 100644 --- a/docs/hello_world.html +++ b/docs/hello_world.html @@ -83,7 +83,7 @@ diff --git a/docs/http.html b/docs/http.html index 0e4e600a9b..40fee69ee4 100644 --- a/docs/http.html +++ b/docs/http.html @@ -83,7 +83,7 @@ diff --git a/docs/index.html b/docs/index.html index dfa3b2d2ca..5945f0c4b2 100644 --- a/docs/index.html +++ b/docs/index.html @@ -83,7 +83,7 @@ diff --git a/docs/installation.html b/docs/installation.html index 213b05d88b..5f5d2e3b87 100644 --- a/docs/installation.html +++ b/docs/installation.html @@ -83,7 +83,7 @@ diff --git a/docs/internet_computer_overview.html b/docs/internet_computer_overview.html index a54053cbb1..7b4137bdf6 100644 --- a/docs/internet_computer_overview.html +++ b/docs/internet_computer_overview.html @@ -83,7 +83,7 @@ diff --git a/docs/limitations.html b/docs/limitations.html new file mode 100644 index 0000000000..b7152297c0 --- /dev/null +++ b/docs/limitations.html @@ -0,0 +1,226 @@ + + + + + + Limitations - The Azle Book + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + +
+
+

Limitations TL;DR

+

There are a number of limitations that you are likely to run into while you develop with Azle on ICP. These are generally the most limiting:

+
    +
  • 5 billion instruction limit for query calls (HTTP GET requests) (~1 second of computation)
  • +
  • 20 billion instruction limit for update calls (HTTP POST/etc requests) (~5 seconds of computation)
  • +
  • 2 MiB request size limit
  • +
  • 3 MiB response size limit
  • +
  • 4 GiB heap limit
  • +
  • High request latency relative to traditional web applications (think seconds not milliseconds)
  • +
  • High costs relative to traditional web applications (think ~10x traditional web costs)
  • +
+

Read more here for in-depth information on current ICP limitations.

+ + +
+ + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + +
+ + diff --git a/docs/management_canister.html b/docs/management_canister.html index e098e6ea71..9fd3d5c688 100644 --- a/docs/management_canister.html +++ b/docs/management_canister.html @@ -83,7 +83,7 @@ diff --git a/docs/print.html b/docs/print.html index 8a011785d8..a83f77a5fa 100644 --- a/docs/print.html +++ b/docs/print.html @@ -84,7 +84,7 @@ @@ -621,6 +621,141 @@

Limitations

app.listen();

Assuming the /dist directory in the canister has an appropriate index.html file, this canister would serve a frontend at its URL when loaded in a web browser.

+

Authentication TL;DR

+

Azle canisters can import ic from azle and use ic.caller() to get the principal (public-key linked identifier) of the initiator of an HTTP request. HTTP requests are anonymous (principal 2vxsx-fae) by default, but authentication with web browsers (and maybe Node.js) can be done using a JWT-like API from azle/http_client.

+

First you import toJwt from azle/http_client:

+
import { toJwt } from 'azle/http_client';
+
+

Then you use fetch and construct an Authorization header using an @dfinity/agent Identity:

+
const response = await fetch(
+    `http://bkyz2-fmaaa-aaaaa-qaaaq-cai.localhost:8000/whoami`,
+    {
+        method: 'GET',
+        headers: [['Authorization', toJwt(this.identity)]]
+    }
+);
+
+

Here's an example of the frontend of a simple web application using azle/http_client and Internet Identity:

+
import { Identity } from '@dfinity/agent';
+import { AuthClient } from '@dfinity/auth-client';
+import { toJwt } from 'azle/http_client';
+import { html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+
+@customElement('azle-app')
+export class AzleApp extends LitElement {
+    @property()
+    identity: Identity | null = null;
+
+    @property()
+    whoami: string = '';
+
+    connectedCallback() {
+        super.connectedCallback();
+        this.authenticate();
+    }
+
+    async authenticate() {
+        const authClient = await AuthClient.create();
+        const isAuthenticated = await authClient.isAuthenticated();
+
+        if (isAuthenticated === true) {
+            this.handleIsAuthenticated(authClient);
+        } else {
+            await this.handleIsNotAuthenticated(authClient);
+        }
+    }
+
+    handleIsAuthenticated(authClient: AuthClient) {
+        this.identity = authClient.getIdentity();
+    }
+
+    async handleIsNotAuthenticated(authClient: AuthClient) {
+        await new Promise((resolve, reject) => {
+            authClient.login({
+                identityProvider: import.meta.env.VITE_IDENTITY_PROVIDER,
+                onSuccess: resolve as () => void,
+                onError: reject,
+                windowOpenerFeatures: `width=500,height=500`
+            });
+        });
+
+        this.identity = authClient.getIdentity();
+    }
+
+    async whoamiUnauthenticated() {
+        const response = await fetch(
+            `${import.meta.env.VITE_CANISTER_ORIGIN}/whoami`
+        );
+        const responseText = await response.text();
+
+        this.whoami = responseText;
+    }
+
+    async whoamiAuthenticated() {
+        const response = await fetch(
+            `${import.meta.env.VITE_CANISTER_ORIGIN}/whoami`,
+            {
+                method: 'GET',
+                headers: [['Authorization', toJwt(this.identity)]]
+            }
+        );
+        const responseText = await response.text();
+
+        this.whoami = responseText;
+    }
+
+    render() {
+        return html`
+            <h1>Internet Identity</h1>
+
+            <h2>
+                Whoami principal:
+                <span id="whoamiPrincipal">${this.whoami}</span>
+            </h2>
+
+            <button
+                id="whoamiUnauthenticated"
+                @click=${this.whoamiUnauthenticated}
+            >
+                Whoami Unauthenticated
+            </button>
+            <button
+                id="whoamiAuthenticated"
+                @click=${this.whoamiAuthenticated}
+                .disabled=${this.identity === null}
+            >
+                Whoami Authenticated
+            </button>
+        `;
+    }
+}
+
+

Here's an example of the backend of that same simple web application:

+
import { ic } from 'azle';
+import express from 'express';
+
+const app = express();
+
+app.get('/whoami', (req, res) => {
+    res.send(ic.caller().toString());
+});
+
+app.use(express.static('/dist'));
+
+app.listen();
+
+

Authentication

+

Examples:

+ +

Under-the-hood

+

Authentication of ICP calls is done through signatures on messages. @dfinity/agent provides very nice abstractions for creating all of the required signatures in the correct formats when calling into canisters on ICP. Unfortunately this requires you to abandon traditional HTTP requests, as you must use the agent's APIs.

+

Azle attempts to enable you to perform traditional HTTP requests with traditional libraries. Currently Azle focuses on fetch. When importing toJwt, azle/http_client will overwrite the global fetch function and will intercept fetch requests that have Authorization headers with an Identity as a value.

+

Once intercepted, these requests are turned into @dfinity/agent requests that call the http_request and http_request_update canister methods directly, thus performing all of the required client-side authentication work.

+

We are working to push for ICP to more natively understand JWTs for authentication, without the need to intercept fetch requests and convert them into agent requests.

Debugging TL;DR

If your terminal logs ever say did not produce a response or response failed classification=Status code: 502 Bad Gateway, it most likely means that your canister has thrown an error and halted execution for that call. Use console.log and try/catch liberally to track down problems and reveal error information. If your error logs do not have useful messages, use try/catch with a console.log of the catch error argument to reveal the underlying error message.

Debugging

@@ -695,6 +830,34 @@

Limitations TL;DR

+

There are a number of limitations that you are likely to run into while you develop with Azle on ICP. These are generally the most limiting:

+ +

Read more here for in-depth information on current ICP limitations.

+

Reference