Import and Subinvoke
You're reading Part One of the Create Wraps tutorial, where we learn everything you need to know to productively develop Polywrap wraps.
It's time to make the obscure
method a little more interesting. In the previous section, we made the data hard to decipher by shifting each character in each string by a fixed amount. We can make our method far more insidious by hashing the data instead.
To do this, we can import the Sha3
wrap and subinvoke one of its hashing functions. First we'll learn how to import a wrap using a Wrap URI, and then we'll learn how to subinvoke the method using its generated Module bindings.
Your dependencies don't have to be other wraps. You can also use language-specific libraries, such as the Rust sha3
package that was used to build the Sha3
wrap.
Wrap URIs
Wrap URIs are a simple, flexible URI format used by Polywrap to identify and import wraps. They allow wraps to be stored and retrieved from a variety of sources, including IPFS, HTTP, and the local filesystem.
Wrap URIs have the benefit of decentralization: developers are not required to use a centralized registry to publish their wraps. Instead, they can publish their wraps to any storage network and resolve them using a Polywrap Client.
A Wrap URI has three parts:
- Scheme (optional):
wrap://
- Authority: e.g.
http
,ipfs
,fs
- Path: e.g.
ipfs/Qm...
,http/https://...
,fs/path/to/wrap
You can learn more about the Wrap URI format in the Wrap URI specification.
A wrap URI is valid so long as it follows the Wrap URI format and can be resolved by a Polywrap Client. The Polywrap Client resolves URIs using implementations of the UriResolver
interface. The Polywrap Client's default configuration includes several resolvers for common URI authorities, including IPFS, HTTP, ENS, and the local filesystem. Users can also configure the Polywrap Client with custom resolvers to resolve URIs from other sources.
Import a Wrap
We'll import our first dependency using a Wrap URI with the wrapscan.io
authority. The URI points to Polywrap's own Wrapscan registry, which is still in construction as of the time of this writing.
The syntax of an import statement is:
#import { <type1, type2, ...> } into <Namespace> from <WrapUri>
Import statements are placed in the Wrap Schema. Let's add an import statement for the Sha3 wrap.
#import { Module } into Sha3 from "wrapscan.io/polywrap/sha3@1.0.0"
type Module {
obscure(data: [String!]!, chaosLevel: Int): String!
}
This import statement ensures a Module
type will be available in the Sha3
namespace. Make sure to run codegen once more to update the bindings.
Subinvocations
Once we've successfully updated our generated bindings for the Sha3 dependency, we can use them to invoke a hash function.
But how do we know which hash functions are available in the Sha3 wrap? Once again, the Wrap Schema comes in clutch. Even without package documentation, we can always know a wrap's interface by looking at its schema. You can view the schema for the Sha3
wrap on Github or Wrapscan. The schemas of wraps pinned on IPFS to the https://ipfs.wrappers.io
gateway can also be viewed on Wrappers.io.
Great places to find wraps
- Awesome Polywrap Github Repo
- Wrapscan Registry (under construction)
- Wrappers.io list of all wrappers pinned to
https://ipfs.wrappers.io
We'll use the keccak256
hash function for the tutorial. Import the Sha3 module and the Args type for the keccak256 method. The imported Sha3 module exposes its functions as static methods, so you can call them without creating an instance.
- Rust
- Go
- TypeScript
- AssemblyScript
pub mod wrap;
pub use wrap::prelude::*;
use crate::wrap::imported::ArgsKeccak256;
impl ModuleTrait for Module {
fn obscure(args: ArgsObscure) -> Result<String, String> {
// handle default values
let chaos_level = args.chaos_level.unwrap_or(1).max(1);
let mut obscured = String::new();
for data in &args.data {
let mut message = data.clone();
for _ in 0..chaos_level {
message = Sha3Module::keccak_256(&ArgsKeccak256 { message })?;
}
obscured += &message;
}
Ok(obscured)
}
}
package module
import (
"example.com/template-wasm-go/module/wrap/types"
"example.com/template-wasm-go/module/wrap/imported/sha3"
)
func Obscure(args *types.ArgsObscure) string {
// Handle default values
chaosLevel := int32(1)
if args.ChaosLevel != nil && *args.ChaosLevel >= 1 {
chaosLevel = *args.ChaosLevel
}
var obscured string
for _, data := range args.Data {
tempData := data
for i := int32(0); i < chaosLevel; i++ {
hashArgs := &sha3.Sha3_ArgsKeccak_256 { Message: tempData }
hashed, err := sha3.Sha3_Keccak_256(hashArgs)
if err != nil {
return ""
}
tempData = hashed
}
obscured += tempData
}
return obscured
}
import { ModuleBase, Args_obscure, Sha3_Module } from './wrap';
export class Module extends ModuleBase {
obscure(args: Args_obscure): string {
// handle default values
const chaosLevel = args.chaosLevel || 1;
// obscure the data with chaos
let obscured: string = "";
for (let i = 0; i < args.data.length; ++i) {
let data = args.data[i];
for (let j = 0; j < chaosLevel; ++j) {
const result = Sha3_Module.keccak_256({ message: data })
if (!result.ok) throw Error("hash failed");
data = result.value!!;
}
obscured += data;
}
return obscured;
}
}
import { ModuleBase, Args_obscure, Sha3_Module } from './wrap';
export class Module extends ModuleBase {
obscure(args: Args_obscure): string {
// handle default values
const chaosLevel: i32 = (args.chaosLevel == null || args.chaosLevel!!.unwrap() < 1)
? 1
: args.chaosLevel!!.unwrap();
let obscured: string = "";
for (let i = 0; i < args.data.length; ++i) {
let data = args.data[i];
for (let j = 0; j < chaosLevel; ++j) {
data = Sha3_Module.keccak_256({ message: data }).expect("hash failed");
}
obscured += data;
}
return obscured;
}
}
When one wrap invokes another, we call it a subinvocation. Subinvocations are a special feature of Polywrap that aren't available in ordinary WebAssembly modules. The wrap standard makes it possible to invoke the Sha3 wrap from any wrap or Polywrap Client without knowing its source language.
Subinvocations never throw errors. The return type of a subinvocation is always the Result
type, or a language-specific equivalent. The Result
type wraps the return value and contains either the expected value or an error.
Next Steps
Next we'll talk about interfaces and plugins. Interfaces are a powerful feature that allows wraps to implement a standard API. Plugins are extensions of a Polywrap Client, written in the client's host language, that give wraps access to host capabilities like networking and the filesystem.