-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
251 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string>, | ||
symbol: Option<string>, | ||
decimals: number, | ||
data: { | ||
total_supply: bigint, | ||
allowances: (key: [SS58String, SS58String]) => Promise<Result<bigint>>, | ||
balances: (key: SS58String) => Promise<Result<bigint>> | ||
} | ||
} | ||
*/ | ||
} | ||
``` | ||
|
||
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); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters