From 9f574bb0b3c0b69064956d0225c1033f90f1d443 Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Mon, 30 Dec 2024 14:00:28 +0100 Subject: [PATCH] feat(ink-sdk): add docs for ink sdk --- docs/pages/ink.md | 2 +- docs/pages/sdks/ink-sdk.md | 230 +++++++++++++++++++++++++++++++++++++ docs/pages/sdks/intro.md | 9 ++ vocs.config.tsx | 15 ++- 4 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 docs/pages/sdks/ink-sdk.md create mode 100644 docs/pages/sdks/intro.md diff --git a/docs/pages/ink.md b/docs/pages/ink.md index 4498064d..f189873a 100644 --- a/docs/pages/ink.md +++ b/docs/pages/ink.md @@ -2,7 +2,7 @@ Polkadot-API adds typescript definitions for ink! contracts, as well as utilities to encode and decode messages, contract storage and events. -The ink client with type support can be found at `polkadot-api/ink`. It's chain-agnostic, meaning it doesn't dictate which runtime APIs, storage or transactions are required. For a more integrated ink! support for specific chains, a Polkadot-API SDK for ink! contracts is currently in development. +The ink client with type support can be found at `polkadot-api/ink`. It's chain-agnostic, meaning it doesn't dictate which runtime APIs, storage or transactions are required. For a more integrated ink! support for specific chains, see [Ink! SDK](/sdks/ink-sdk). ## Codegen diff --git a/docs/pages/sdks/ink-sdk.md b/docs/pages/sdks/ink-sdk.md new file mode 100644 index 00000000..97a8c350 --- /dev/null +++ b/docs/pages/sdks/ink-sdk.md @@ -0,0 +1,230 @@ +# Ink! SDK + +The Ink! SDK is a library for interacting with smart contracts, built on top of the [Ink! Client](/ink). + +## Getting Started + +Begin by generating the type definitions for your chain and contract. For example, using a PSP22 contract on the test Aleph Zero network: + +```sh +pnpm papi add -w wss://aleph-zero-testnet-rpc.dwellir.com testAzero +pnpm papi ink add ./psp22.json # Path to the .contract or .json metadata file +``` + +This process uses the name defined in the contract metadata to export it as a property of `contracts` in `@polkadot-api/descriptors`. For this example, the contract name is "psp22." You can now instantiate the Ink! SDK: + +```ts +import { contracts, testAzero } from "@polkadot-api/descriptors"; +import { createInkSdk } from "@polkadot-api/sdk-ink"; +import { createClient } from "polkadot-api"; +import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat"; +import { getWsProvider } from "polkadot-api/ws-provider/web"; + +const client = createClient( + withPolkadotSdkCompat( + getWsProvider("wss://aleph-zero-testnet-rpc.dwellir.com"), + ), +); +const typedApi = client.getTypedApi(testAzero); + +const psp22Sdk = createInkSdk(typedApi, contracts.psp22); +``` + +The SDK provides two main functions for different workflows: + +- `getDeployer(code: Binary)`: Returns an API for deploying contracts. +- `getContract(address: SS58String)`: Returns an API for interacting with deployed contracts. + +## Contract Deployer + +```ts +import { Binary } from "polkadot-api"; + +const wasmBlob = ...; // Uint8Array of the contract WASM blob. +const code = Binary.fromBytes(wasmBlob); + +const psp22Deployer = psp22Sdk.getDeployer(code); +``` + +The deployer API supports two methods: one for dry-running deployments and another for actual deployments. + +When deploying, the SDK calculates all parameters, requiring only the constructor arguments defined in the contract. + +To calculate the gas limit required, the SDK also needs the origin account the transaction will be sent from. So it either uses the origin account or the gas limit if you already have it. + +The "salt" parameter ensures unique contract deployments. By default, it is empty, but you can provide a custom value for deploying multiple instances of the same contract. + +```ts +// Deploy psp22 +const ALICE = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; +const tx = psp22Deployer.deploy("new", { + origin: ALICE, + data: { + supply: 1_000_000_000_000n, + decimals: 9, + }, +}); + +// `tx` is a regular transaction that can be sent with `.signSubmitAndWatch`, `.signAndSubmit`, etc. +const result = await tx.signAndSubmit(aliceSigner); + +// To get the resulting address the contract was deployed to, we can pass the events back into the SDK: +const data: { + address: string; + contractEvents: EventDescriptor[]; +} | null = psp22Sdk.readDeploymentEvents(ALICE, result.events); +``` + +Dry-running takes in the same arguments, but returns a Promise with the result directly instead: + +```ts +const dryRunResult = await psp22Deployer.deploy("new", { + origin: ALICE, + data: { + supply: 1_000_000_000_000n, + decimals: 9, + }, +}); + +if (dryRunResult.success) { + console.log(dryRunResult.value); + /* + dryRunResult.value has: + { + // Resulting address of contract + address: SS58String; + // Events that would be generated when deploying + events: EventDescriptor[]; + // Weight required + gasRequired: Gas; + } + */ +} +``` + +## Contract API + +The contract API targets a specific instance of a contract by address, providing multiple interaction functions: + +```ts +const PSP22_INSTANCE = "5F69jP7VwzCp6pGZ93mv9FkAhwnwz4scR4J9asNeSgFPUGLq"; +const psp22Contract = psp22Sdk.getContract(PSP22_INSTANCE); + +// You optionally can make sure the hash hasn't changed by checking compatibility +if (!await psp22Contract.isCompatible()) { + throw new Error("Contract has changed"); +} +``` + +### Query + +Sending a query (also known as dry-running a message), can be sent directly through the `.query()` method, passing the name of the message, origin and arguments: + +```ts +console.log("Get balance of ALICE") +const result = await psp22Contract.query("PSP22::balance_of", { + origin: ALICE, + data: { + owner: ALICE, + }, +}) + +if (result.success) { + console.log("balance of alice", result.value.response) + console.log("events", result.value.events) +} else { + console.log("error", result.value) +} +``` + +### Send + +Sending a message requires signing a transaction, which can be created with the `.send()` method: + +```ts +console.log("Increase allowance"); +const allowanceTxResult = await psp22Contract + .send("PSP22::increase_allowance", { + origin: ADDRESS.alice, + data, + }) + .signAndSubmit(aliceSigner) + +if (allowanceTxResult.ok) { + console.log("block", allowanceTxResult.block) + // The events generated by this contract can also be filtered using `filterEvents`: + console.log("events", psp22Contract.filterEvents(allowanceTxResult.events)) +} else { + console.log("error", allowanceTxResult.dispatchError) +} +``` + +### Redeploy + +Contract instances can also be redeployed without the need to have the actual WASM blob. This is similar to using the [Contract Deployer](#contract-deployer), but it's done directly from the contract instance: + +```ts +const salt = Binary.fromHex("0x00") +const result = await psp22Contract.dryRunRedeploy("new", { + data, + origin: ADDRESS.alice, + options: { + salt, + }, +}) + +if (result.success) + console.log("redeploy dry run", result) + +const txResult = await psp22Contract + .redeploy("new", { + data, + origin: ADDRESS.alice, + options: { + salt, + }, + }) + .signAndSubmit(signer) + +const deployment = psp22Sdk.readDeploymentEvents( + ADDRESS.alice, + txResult.events, +) +``` + +### Storage API + +The storage of a contract is a tree structure, where you can query the values that are singletons, but for those that can grow into lists, maps, etc. they have to be queried separately. + +This SDK has full typescript support for storage. You start by selecting where to begin from the tree, and you'll get back an object with the data within that tree. + +```ts +const storage = psp22Contract.getStorage(); + +const root = await storage.getRoot(); +if (root.success) { + /* result.value is what the psp22 contract has defined as the root of its storage + { + name: Option, + symbol: Option, + decimals: number, + data: { + total_supply: bigint, + allowances: (key: [SS58String, SS58String]) => Promise>, + balances: (key: SS58String) => Promise> + } + } + */ +} +``` + +If inside of that subtree there are storage entries that have to be queried separately, the SDK turns those properties into functions that query that. In the example above, `data.allowances` and `data.balances` are maps that require one specific key to perform the query, so they have been turned into async functions. + +If you don't need the data of the root and just want to query for a specific balance directly, you can get any nested subtree by calling `.getNested()`: + +```ts +const aliceBalance = await storage.getNested("data.balances", ALICE); +if (aliceBalance.success) { + console.log("Alice balance", aliceBalance.value); +} +``` diff --git a/docs/pages/sdks/intro.md b/docs/pages/sdks/intro.md new file mode 100644 index 00000000..0e281417 --- /dev/null +++ b/docs/pages/sdks/intro.md @@ -0,0 +1,9 @@ +# Polkadot-API SDKs + +The Polkadot-API library is designed to be modular and chain-agnostic, meaning it does not include any business-level logic. It reads the chain metadata to understand how to interact with the chain, but it is up to the developer to define its specific use and purpose. + +With its modular design philosophy, Polkadot-API enables the creation of additional libraries that add new layers of abstraction, significantly enhancing the developer experience when building dApps. These additional libraries are referred to as "Polkadot-API SDKs." + +These SDKs abstract implementation details from the pallets, elevating chain interactions to a higher level of abstraction. They handle complexities such as transaction weight calculation, associated fees, and determining the best transaction (or set of transactions) for performing specific actions. + +We have created several SDKs, documented in the following sections, to simplify common interactions when working with Substrate-based chains. These SDKs not only streamline development but can also serve as examples for anyone interested in creating their own SDKs. diff --git a/vocs.config.tsx b/vocs.config.tsx index 9ba793e2..1cb911c1 100644 --- a/vocs.config.tsx +++ b/vocs.config.tsx @@ -60,11 +60,8 @@ export default defineConfig({ }, { text: "Typed API", + link: "/typed", items: [ - { - text: "API", - link: "/typed", - }, { text: "Constants", link: "/typed/constants", @@ -97,6 +94,16 @@ export default defineConfig({ }, ], }, + { + text: "PAPI SDKs", + link: "/sdks/intro", + items: [ + { + text: "Ink! SDK", + link: "/sdks/ink-sdk", + }, + ], + }, { text: "Chain-specific documentation", link: "https://chains.papi.how",