Start now →

I Built an ethers.js-Style SDK for Hyperledger Fabric in Go

By Muhammad Talha · Published April 23, 2026 · 5 min read · Source: Blockchain Tag
EthereumDeFi
I Built an ethers.js-Style SDK for Hyperledger Fabric in Go
Press enter or click to view image in full size

I Built an ethers.js-Style SDK for Hyperledger Fabric in Go

Muhammad TalhaMuhammad Talha4 min read·Just now

--

The boilerplate problem that every Fabric developer knows too well

If you’ve ever built on Ethereum, you know how clean the developer experience feels:

const token = new ethers.Contract(address, abi, signer)
const balance = await token.balanceOf(addr) // read
const tx = await token.transfer(to, amount) // write
await tx.wait()

Three lines. The library handles the RPC connection, wallet signing, and confirmation waiting. You focus on your application logic.

Now try doing the same thing with Hyperledger Fabric in Go.

What you actually write today

Before you can call a single chaincode function, the official Fabric Gateway SDK requires you to wire up TLS certificates, gRPC connections, identity loading, and gateway configuration manually — every time, in every service:

certPEM, _ := os.ReadFile(config.TLSCertPath)
cert, _ := identity.CertificateFromPEM(certPEM)
pool := x509.NewCertPool()
pool.AddCert(cert)
conn, _ := grpc.Dial(config.PeerEndpoint,
grpc.WithTransportCredentials(
credentials.NewClientTLSFromCert(pool, config.PeerHostOverride),
),
)
id, _ := loadIdentityFromDir(config.CertPath, config.MSPID)
sign, _ := loadSignerFromDir(config.KeyPath)
gw, _ := client.Connect(id,
client.WithSign(sign),
client.WithHash(hash.SHA256),
client.WithClientConnection(conn),
client.WithEvaluateTimeout(5*time.Second),
client.WithEndorseTimeout(15*time.Second),
client.WithSubmitTimeout(5*time.Second),
client.WithCommitStatusTimeout(1*time.Minute),
)
network := gw.GetNetwork(config.ChannelName)
contract := network.GetContractWithName(config.ChaincodeName, config.ContractName)

And that’s just the connection. Every chaincode function is another 15–20 lines on top of that — marshalling, SubmitAsync, commit.Status(), and manually digging through gRPC status details to find the actual chaincode error message buried inside a proto.

On a real project with multiple chaincodes and multiple services, this becomes hundreds of lines of pure infrastructure duplication.

Introducing fabricsdk

I spent time abstracting all of this into a single reusable Go package:

go get github.com/muhammadtalha198/fabricsdk

Here is the exact same setup, rewritten:

cfg := fabricsdk.Config{
PeerEndpoint: "localhost:7051",
PeerHostOverride: "peer0.org1.example.com",
TLSCertPath: "/path/to/peer-tls-ca.pem",
CertPath: "/path/to/msp/signcerts",
KeyPath: "/path/to/msp/keystore",
MSPID: "Org1MSP",
ChannelName: "mychannel",
ChaincodeName: "mychaincode",
}
fc, err := fabricsdk.New(cfg)
if err != nil { log.Fatal(err) }
defer fc.Close()
ctx := context.Background()// Read — no ledger write
raw, err := fc.Evaluate(ctx, "GetAsset", "asset-1")
// Write — endorse, submit, wait for commit, return txID
txID, err := fc.Submit(ctx, "CreateAsset", string(jsonBytes))

That is the entire setup. No manual gRPC wiring. No TLS pool construction. No proto digging.

How it maps to ethers.js

The concepts translate directly. JsonRpcProvider becomes your Config.PeerEndpoint. Your Wallet private key becomes Config.CertPath and Config.KeyPath — the x509 identity Fabric uses instead of a raw private key. new Contract(address, abi, signer) becomes fabricsdk.New(cfg).

Reading with token.balanceOf(addr) becomes fc.Evaluate(ctx, "balanceOf", addr). Writing with token.transfer(to, amt) becomes fc.Submit(ctx, "transfer", to, amt). The transaction hash is the txID string returned by Submit. And await tx.wait() is already built into Submit — it blocks until the block is committed before returning.

One thing that is actually simpler than ethers.js: no ABI file needed. In Ethereum you need a JSON ABI to describe what functions exist on a contract. In Fabric, chaincode functions are called by name string directly. fc.Submit("AnyFunctionName", arg1, arg2) works for any chaincode deployed now or in the future, with zero SDK changes.

What else the SDK handles

Per-call identity override

Fabric is permission-based — different org members sign different transactions. The SDK supports this without opening a new TCP connection:

// Default identity (from Config)
txID, err := fc.Submit(ctx, "CreateAsset", payload)
// Admin identity for a single call — same gRPC connection, different signer
txID, err = fc.WithIdentity("/admin/signcerts", "/admin/keystore").
Submit(ctx, "DeleteAsset", "asset-1")

This is equivalent to token.connect(adminSigner) in ethers.js.

Structured errors — no string parsing

The official SDK buries the real chaincode error message inside nested gRPC status details. The SDK extracts it and returns a typed error with an HTTP-style status code:

txID, err := fc.Submit(ctx, "CreateAsset", payload)
if fabricsdk.IsNotFound(err)     { /* 404 */ }
if fabricsdk.IsConflict(err) { /* 409 — optimistic lock, re-read and retry */ }
if fabricsdk.IsUnauthorized(err) { /* 403 */ }
var fabErr *fabricsdk.Error
if errors.As(err, &fabErr) {
http.Error(w, fabErr.Message, fabErr.Code) // forward directly to API handler
}

Chaincode event streaming

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
events, err := fc.Events(ctx)
for ev := range events {
fmt.Printf("event=%s txID=%s payload=%s\n",
ev.EventName, ev.TxID, ev.Payload)
}
// Replay from a known block after a restart
events, err = fc.EventsFrom(ctx, lastKnownBlock)

Dynamic multi-chaincode calls

When you need to call multiple chaincodes from one connection without reconfiguring:

dyn := fc.Dynamic()
raw,  err := dyn.Evaluate(ctx, "channelA", "ccA", "ContractA", "GetAsset", "id-1")
txID, err := dyn.Submit(ctx, "channelB", "ccB", "ContractB", "CreateAsset", payload)

Pluggable logger

type zapAdapter struct{ l *zap.SugaredLogger }
func (a *zapAdapter) Info(msg string, kv ...any) { a.l.Infow(msg, kv...) }
func (a *zapAdapter) Error(msg string, kv ...any) { a.l.Errorw(msg, kv...) }
cfg.Logger = &zapAdapter{l: logger.Sugar()}

The recommended pattern: thin typed wrappers

For production applications, build a small domain-specific wrapper on top of the generic SDK. This is the same typed-wrappers pattern you’d use with ethers.js and TypeScript:

// internal/asset/client.go
package asset
type Client struct{ fc *fabricsdk.FabricClient }func NewClient(fc *fabricsdk.FabricClient) *Client { return &Client{fc} }func (c *Client) Create(req CreateRequest) (string, error) {
b, _ := json.Marshal(req)
return c.fc.Submit(context.Background(), "CreateAsset", string(b))
}
func (c *Client) Get(id string) (*Asset, error) {
raw, err := c.fc.Evaluate(context.Background(), "GetAsset", id)
if err != nil { return nil, err }
var a Asset
json.Unmarshal(raw, &a)
return &a, nil
}

A new chaincode becomes one small wrapper file. The connection, identity, and error handling code never gets touched again.

The numbers

Before the SDK, connecting and calling one chaincode function required roughly 60 lines for setup and 20 lines per function. With fabricsdk, setup is 10 lines and each function is 3–4 lines. Error classification that previously needed manual gRPC proto inspection is now a single predicate call. Identity overrides that previously required rebuilding the entire gateway are now one chained method.

Get it

go get github.com/muhammadtalha198/fabricsdk

Full documentation: https://pkg.go.dev/github.com/muhammadtalha198/fabricsdk

GitHub: https://github.com/muhammadtalha198/fabricsdk

MIT licensed. Requires Go 1.21+.

Issues, feedback, and PRs are welcome. If you’re building on Hyperledger Fabric and have spent time copying the same gRPC boilerplate between services — this was built for exactly that problem.

This article was originally published on Blockchain Tag and is republished here under RSS syndication for informational purposes. All rights and intellectual property remain with the original author. If you are the author and wish to have this article removed, please contact us at [email protected].

NexaPay — Accept Card Payments, Receive Crypto

No KYC · Instant Settlement · Visa, Mastercard, Apple Pay, Google Pay

Get Started →