How to Package a Web3 Protocol? Learnings from TalentLayer’s SDK Development

Most protocols in web3 have an npm package. Follow this article to understand if your protocol needs one too, and how to build one!

Pranav singhal
TalentLayer
9 min readNov 11, 2023

--

TalentLayer’s SDK

In October 2023 during ETHRome the we launched the TalentLayer SDK. This was a very exciting step for our ecosystem! A good SDK is essential for easy developer adoption of all protocols and infras. Thus, we wanted to share our learnings with you — hope it can be helpful to other open-source teams as they embark on their journey to easier adoption.

Introduction: Crafting Your SDK’s Foundation

When embarking on the creation of an npm package for a web3 protocol, it’s clear that the process extends beyond just setting up a package.json and an index.ts file. It's about making strategic choices that will shape the functionality and usability of the SDK.

Envisioning the Inaugural Release: v0.0.1

The initial version, v0.0.1, will set the precedent for what’s to come. It’s essential to consider what features and capabilities should be included in this foundational release. How will it introduce the core concepts of your protocol to the developer community? How will you validate its use-case ( kind of a product-developer-fit) ? Will it be specific to a framework?

These are the questions I had to answer when structuring the TalentLayer SDK. In this blog, I will walk you through all the decisions we had to make to create the first release of the TalentLayer SDK.

If you want to use the SDK to build the future of hiring marketplaces, check it out here: https://www.npmjs.com/package/@talentlayer/client

So Strap in the for the Saga of the Talentlayer SDK:

The First Decision: Choosing the Structure

Our tale begins with the foundational choice of how to structure your SDK. This decision sets the stage for future scalability and maintainability. In the case of TalentLayer, it was clear from the outset that we needed at least two packages, client and react. The client package, being framework-agnostic, is the core that powers other packages, such as react. The directory tree is meticulously planned to accommodate growth, ensuring that as your protocol evolves, your SDK can evolve with it.

Here is the tree diagram for the entire structure

├── package-lock.json
├── package.json
├── packages
│ ├── client
│ │ ├── src
│ │ │ ├── blockchain-bindings
│ │ │ │ ├── chains.ts
│ │ │ │ └── erc20.ts
│ │ │ ├── constants
│ │ │ │ └── index.ts
│ │ │ ├── contracts
│ │ │ │ └── ABI
│ │ │ │ ├── ERC20.json
│ │ │ │ ├── ...
│ │ │ │ └── TalentLayerService.json
│ │ │ ├── disputes
│ │ │ │ ├── index.ts
│ │ │ │ └── types.ts
│ │ │ ├── escrow
│ │ │ │ ├── graphql
│ │ │ │ │ └── queries.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ ├── graphql
│ │ │ │ ├── index.ts
│ │ │ │ └── queries.ts
│ │ │ ├── index.ts
│ │ │ ├── ipfs
│ │ │ │ └── index.ts
│ │ │ ├── platform
│ │ │ │ ├── graphql
│ │ │ │ │ └── queries.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ ├── profile
│ │ │ │ ├── graphql
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ ├── proposals
│ │ │ │ ├── graphql
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ ├── reviews
│ │ │ │ ├── graphql
│ │ │ │ │ └── index.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ ├── services
│ │ │ │ ├── enums
│ │ │ │ │ └── index.ts
│ │ │ │ ├── graphql
│ │ │ │ │ └── queries.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── types
│ │ │ │ └── index.ts
│ │ │ ├── types
│ │ │ │ └── index.ts
│ │ │ ├── utils
│ │ │ │ ├── fees.ts
│ │ │ │ └── signature.ts
│ │ │ └── viem
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── eslint-config-custom
│ │ ├── README.md
│ │ ├── library.js
│ │ ├── next.js
│ │ ├── package.json
│ │ └── react-internal.js
│ └── tsconfig
│ ├── base.json
│ ├── nextjs.json
│ ├── package.json
│ └── react-library.json
├── tsconfig.json
└── turbo.json

This structure looks like a mammoth, but you need only focus on the top-level structure.

├── packages
│ ├── client
│ │ │...
│ ├── react
│ │ │...

The packages folder holds each npm package as a sub-folder. And so, the talentLayer/client ( i.e. the core talentLayer package) is the `client` folder inside the packages `folder`

These packages are managed centrally, through a common tsconfig and eslint-config. Both can be overriden by each package, but they set the standard rules that are followed by all packages.

This is a standard structure that most SDKs follow. You can choose to set it up manually, but I recommend using turbo, an npm package that will do all the inital configuration for you.

Defining the First Package: Framework Agnosticism

The inaugural package in any SDK should stand independent of any particular framework. This universality ensures that it can serve as the backbone of the SDK, supporting various frameworks without becoming obsolete as new technologies emerge. This framework-agnostic philosophy is the cornerstone of building a resilient and future-proof SDK.

This is why the `client` is important. The `talentLayer/client` can run in any js environment. In fact, it is bundled so that it can be used both as a cjs module, or an esm module. Keep reading to see how to make that happen.

The First Component: Embrace the Class

The journey continues with the creation of the first component of your SDK, which should be a class. This encapsulates functionality and provides a clear, familiar interface for developers. Classes offer a structured way to interact with the protocol, making your SDK intuitive to use.

With the structure in place, lets start building…

Sub-Components as Classes: A Hierarchy of Order

As the story unfolds, each sub-component within the SDK also takes the form of a class. This structure ensures a clean and organized hierarchy where each class is responsible for a distinct domain. The main client class serves as the protagonist, initializing and orchestrating the interactions between the sub-component classes.

Here is what this looks like in the entry file for the client:

/**
* Main client for interacting with the TalentLayer protocol.
*/
export class TalentLayerClient {
graphQlClient: GraphQLClient;
ipfsClient: IPFSClient;
viemClient: ViemClient;
platformID: number;
chainId: NetworkEnum;
signatureApiUrl?: string;

/**
* Initializes a new instance of the TalentLayerClient.
* @param {TalentLayerClientConfig} config - Configuration options for the client.
*/
constructor(config: TalentLayerClientConfig) {
...
}


get erc20(): IERC20 {
return new ERC20(this.ipfsClient, this.viemClient, this.platformID, this.chainId);

}


get proposal(): IProposal {
return new Proposal(
this.graphQlClient,
this.ipfsClient,
this.viemClient,
this.platformID,
this.signatureApiUrl,
);
}

get platform(): IPlatform {
return new Platform(this.graphQlClient, this.viemClient, this.platformID, this.ipfsClient);
}


get disputes(): IDispute {
return new Disputes(this.viemClient, this.platformID, this.graphQlClient, this.chainId);
}


get service(): IService {
return new Service(
this.graphQlClient,
this.ipfsClient,
this.viemClient,
this.platformID,
this.signatureApiUrl,
);
}


get profile(): IProfile {
return new Profile(this.graphQlClient, this.ipfsClient, this.viemClient, this.platformID);
}


get review(): IReview {
return new Review(this.graphQlClient, this.ipfsClient, this.viemClient, this.platformID);
}


get escrow(): IEscrow {
return new Escrow(
this.graphQlClient,
this.ipfsClient,
this.viemClient,
this.platformID,
this.chainId,
);
}
}

You might want to take a pause and marvel at the grandeur of this, somehow self-documenting class. Each sub-component is a `get` call away, and neatly wrapped in its own function.

Beyond aesthetics, there are other advantages to structuring like this.

Using classes helps us to keep operations self-explanatory. For example, creating a new gig with the TalentLayer SDK is as straightforward as:

const talentLayerClient = new TalentLayerClient();
const newService = talentLayerClient.service.create();

If you want to look at more example usage of the talentLayer SDK, checkout the links at the bottom.

Now that we know how to built, lets move on to bundling…

The Choice of Bundler: Enter tsup

When it comes to bundling your SDK, tsup shines as the hero of our story. tsup offers a simple yet powerful solution to bundle your packages as both ESM and CJS modules, catering to a wide range of environments with minimal configuration. With just one command—tsup src/index.ts --format cjs,esm --dts—you can achieve a dual-compatible bundle, ensuring your SDK is accessible regardless of the module system used by the developers' environment.

So now that we have structured, built and bundled our sdk, time to release it to the universe

Managing Releases: The Role of Changeset

As our SDK narrative nears its end, we introduce a pivotal character: Changesets. This npm package plays the crucial role of a release manager. It's akin to a sage advisor, ensuring that the process of creating a release for npm is orderly and comprehensive.

Changeset takes the complexity out of versioning and publishing. With its guidance, you can manage everything from tagging to changelog generation with ease. By adding a .changeset/config.json file , developers can describe their changes in a human-readable way, which then gets compiled into a professional changelog. When it’s time to release, a simple command bundles all changes together, bumping package versions and updating changelogs in accordance with semantic versioning principles.

This process ensures that each release tells a clear story of the SDK’s evolution, making it easier for users to follow along with the changes and updates. It not only maintains a historical record of progress but also streamlines the developer experience when updating to newer versions of your SDK.

Concluding Our SDK Saga

In conclusion, the journey of building an SDK for a web3 protocol is one of thoughtful choices and meticulous organization. From deciding on the initial structure to selecting a bundler like tsup that embraces modern module systems, every step is crucial. We must not forget the importance of classes for clear, object-oriented components that form a robust and intuitive hierarchy. And with Changeset, the final act of our story is made seamless, as it manages the release process with wisdom and precision.

Each chapter of this SDK saga has been carefully written to ensure that the final narrative is one of success and ease of use for the developers who will adopt and build upon your protocol. The story doesn’t end here, though — it continues with each developer who uses the SDK to create something new, each contributing their own verse to the ever-growing epic of web3 development.

Important Links

If you want to build using the TalentLayer protocol, checkout these links:

If you are working on your own SDK, checkout these tools:

Get involved in TalentLayer

TalentLayer is a thriving community of open-source developers building towards a shared goal — a better way of working! Learn more about TalentLayer here.

Want to work with us? Reach out here.

--

--

Pranav singhal
TalentLayer

I am Pranav Singhal, first of my name, developer of web-apps, player of guitar, student of physics. Ask me about Ethereum and wallets