This tutorial provides a comprehensive guide for DVS developers to build a simple DVS sample application—a square number calculator. The complete implementation can be found in the example repository. Users can submit values to be calculated through the square number DVS and specify security parameters (i.e., the security threshold to be achieved). The DVS service will return the squared result that meets the specified security threshold. During DVS development, developers only need to focus on defining the business logic, while the SDK handles the underlying security signing and aggregation mechanisms.

The Pell SDK supports multiple programming language frameworks, including Go, Rust, and TypeScript, with Go offering the most comprehensive application development support. Support for additional languages will be expanded in future releases. This tutorial demonstrates DVS development using Go as the primary example.

1.0 Setting Up the DVS Development Environment

To begin DVS development, clone the template project (Go version) provided by Pell. This template project integrates the complete framework and processes required for DVS development, enabling developers to quickly focus on implementing business logic.

git clone <https://github.com/0xPellNetwork/dvs-template.git>

2.0 Developing the DVS

For business logic implementation, developers need to define the gRPC interface and implement the business logic to create a DVS application. The following sections outline the development process for the square number application:

2.1 Defining the Business Method

Following the Pell DVS project specifications, business methods must be defined using gRPC, typically in proto/<project_name>/<module_name>/msg_server.proto. In this example, we define the gRPC service method RequestNumberSquared, which accepts a parameter, calculates its square, and returns the result. The implementation is located in proto/example/squared/dvs_request.proto.

option go_package = "example/dvs/squared/types";
service DVSRequest {
  rpc RequestNumberSquared(RequestNumberSquaredIn)
      returns (RequestNumberSquaredOut) {}
}
message RequestNumberSquaredIn {
  option (cosmos_proto.implements_interface) = "cosmos.base.v1beta1.Msg";
  string number = 1 [
    (gogoproto.customtype) = "cosmossdk.io/math.Int",
    (gogoproto.nullable) = false
  ];
}
message RequestNumberSquaredOut {
  option (cosmos_proto.implements_interface) = "cosmos.base.v1beta1.Msg";
  string squared = 1 [
    (gogoproto.customtype) = "cosmossdk.io/math.Int",
    (gogoproto.nullable) = false
  ];
}

After defining the business method, generate the corresponding Protobuf interface file at dvs/squared/types/msg_server.pb.go:

make proto

2.2 Implementing the Business Method

Following the gRPC service interface definition, developers must implement the business logic. The Pell DVS project recommends implementing the business logic in dvs/<module_name>/server/server.go. In this example, we implement the square number calculation logic in dvs/squared/server/server.go.

import "github.com/0xPellNetwork/dvs-template/dvs/squared/types"

var _ types.DVSRequestServer = RequestServer{}

type RequestServer struct {}

// NewDvsProcessRequestServer returns an implementation of the DvsProcessRequestServer interface
// for the provided Server.
func NewRequestServer() types.DVSRequestServer {
    return &RequestServer{}
}

func (server RequestServer) RequestNumberSquared(ctx context.Context, request *types.RequestNumberSquaredIn) (*types.RequestNumberSquaredOut, error) {
    numInt := request.Number.Int64()

    // Calculate square
    squaredInt := numInt * numInt
    squared := math.NewInt(squaredInt)

    return &types.RequestNumberSquaredOut{
        Squared: squared,
    }, nil
}

2.3 Customizing Signature Digest Generation

For gRPC service results, a digest generation method for signing must be specified to enable validator verification. The Pell SDK provides a ResultHandler interface to define the digest generation method for specific results. If no method is specified, the default approach hashes the proto result.

type IResultHandler interface {
  GetDigest(proto.Message) ([]byte, error)
}

In the square number task example, the task result requires Keccak256 hash verification of the ABI structure during on-chain signature validation.