eas

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 11, 2024 License: BSD-3-Clause Imports: 27 Imported by: 0

README

Ethereum Attestation Service - Go SDK

Go PkgGoDev NewReleases

This repository contains the Ethereum Attestation Service SDK for the Go programming language, used to interact with the Ethereum Attestation Service Protocol.

Ethereum Attestation Service (EAS) is an open-source infrastructure public good for making attestations onchain or offchain.

Go SDK interacts with EAS Smart Contracts deployed on different EVM-compatible blockchains using smart contract bindings. The list off deployed contracts could be found in EAS Contracts README file. EAS contract address for a desired network in that list should be passed as an argument to the client constructor eas.NewCLient.

Installing the Go EAS SDK

Run go get resenje.org/eas from command line in your Go module directory.

Usage

Please refer to the generated package documentation on https://pkg.go.dev/resenje.org/eas, examples bellow and, of course, tests and code in this repository as the last resource of open source projects.

Schemas

Attestations are structured by defining and registering Schemas. Schemas follow the Solidity ABI for acceptable types. Below is a list of current Solidity types and corresponding Go types.

Go type Solidity type
common.Address address
string string
bool bool
[32]byte bytes32
eas.UID bytes32
[]byte bytes
uint8 uint8
uint16 uint16
uint32 uint32
uint64 uint64
uint256 uint256
struct{<name> <type>; <name> <type>;...} tuple = (<type> <name>, <type> <name>...)
slice = []<type> <type>[]
array = [<size>]<type> <type>[<size>]

All supported types can be nested inside tuples (Go structs), fixed-sized arrays (Go arrays) and variable-length arrays (So slices).

Field names

Solidity tuples are represented with Go struct type where names of the struct fields are used for tuple field names. It is possible to set a custom name with Go struct field tag abi. En example of a tuple related type:

type MyTuple struct {
	ID        eas.UID `abi:"id"`
	Msg       string  `abi:"message"`
	Timestamp uint64  `abi:"timeStamp"`
	RawData   []byte  `abi:"raw_data"`
	Sender    common.Address
}

which corresponds to this schema definition:

bytes32 id, string message, uint64 timeStamp, bytes raw_data, address Sender

Examples

Get an existing attestation
package main

import (
	"context"
	"log"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"resenje.org/eas"
)

var (
	endpointSepolia        = "https://ethereum-sepolia-rpc.publicnode.com/"
	contractAddressSepolia = common.HexToAddress("0xC2679fBD37d54388Ce493F1DB75320D236e1815e")
)

func main() {
	ctx := context.Background()

	privateKey, err := crypto.GenerateKey()
	if err != nil {
		log.Fatal(err)
	}

	c, err := eas.NewClient(ctx, endpointSepolia, privateKey, contractAddressSepolia, nil)
	if err != nil {
		log.Fatal(err)
	}

	attestationUID := eas.HexDecodeUID("0xac812932f5cee90a457d57a9fbd7b142b21ba99b809f982bbf86947f295281ff")

	a, err := c.EAS.GetAttestation(ctx, attestationUID)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Attestation UID", a.UID)
	log.Println("Attestation Time", a.Time)

	var schemaUID eas.UID
	var name string

	if err := a.ScanValues(&schemaUID, &name); err != nil {
		log.Fatal(err)
	}

	log.Println("Attestation")
	log.Println("Schema UID:", schemaUID)
	log.Println("Name:", name)
}
Structured schema

Create a structured schema, make attestation and get attestation.

// Attest a road trip by defining a schema.
package main

import (
	"context"
	"log"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"resenje.org/eas"
)

var (
	endpointSepolia        = "https://ethereum-sepolia-rpc.publicnode.com/"
	contractAddressSepolia = common.HexToAddress("0xC2679fBD37d54388Ce493F1DB75320D236e1815e")
)

type RoadTrip struct {
	ID           uint64      `abi:"id"`
	VIN          string      `abi:"vin"` // Vehicle Identification Number
	VehicleOwner string      `abi:"vehicleOwner"`
	Passengers   []Passenger `abi:"passengers"`
}

type Passenger struct {
	Name     string `abi:"name"`
	CanDrive bool   `abi:"canDrive"`
}

func (p Passenger) CanDriveString() string {
	if p.CanDrive {
		return "can drive"
	}
	return "cannot drive"
}

// Attest a road trip by defining a schema.
func main() {
	ctx := context.Background()

	// Use a fake key here. Use your own funded key to be able to send transactions.
	privateKey, err := eas.HexParsePrivateKey("a896e1f28a6453e8db4794f11ea185befd04c4e4f06790e37e8d1cc90a611948")
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Wallet address:", crypto.PubkeyToAddress(privateKey.PublicKey))

	// Construct a client that will interact with EAS contracts.
	c, err := eas.NewClient(ctx, endpointSepolia, privateKey, contractAddressSepolia, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Create the Schema on chain.
	tx, waitRegistration, err := c.SchemaRegistry.Register(ctx, eas.MustNewSchema(RoadTrip{}), common.Address{}, true)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Waiting schema registration transaction:", tx.Hash())
	schemaRegistration, err := waitRegistration(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Just check the schema definition.
	schema, err := c.SchemaRegistry.GetSchema(ctx, schemaRegistration.UID)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Schema UID:", schema.UID)
	log.Println("Schema:", schema.Schema)

	// Attest a road trip on chain.
	tx, waitAttestation, err := c.EAS.Attest(ctx,
		schema.UID,
		&eas.AttestOptions{Revocable: true},
		RoadTrip{
			ID:           4242,
			VIN:          "1FA6P8CF5L5100421",
			VehicleOwner: "Richard Hammond",
			Passengers: []Passenger{
				{
					Name:     "James May",
					CanDrive: true,
				},
				{
					Name:     "Jeremy Clarkson",
					CanDrive: true,
				},
				{
					Name:     "The Stig",
					CanDrive: false,
				},
			},
		},
	)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Waiting attest transaction:", tx.Hash())
	attestConfirmation, err := waitAttestation(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Get the attestation to verify it.
	a, err := c.EAS.GetAttestation(ctx, attestConfirmation.UID)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Attestation UID", a.UID)
	log.Println("Attestation Time", a.Time)

	var roadTrip RoadTrip
	if err := a.ScanValues(&roadTrip); err != nil {
		log.Fatal(err)
	}

	log.Println("Road trip:", roadTrip.ID)
	log.Println("Vehicle Identification Number:", roadTrip.VIN)
	log.Println("Vehicle owner:", roadTrip.VehicleOwner)
	for i, p := range roadTrip.Passengers {
		log.Printf("Passenger %v: %s (%s)", i, p.Name, p.CanDriveString())
	}
}

Versioning

Each version of the client is tagged and the version is updated accordingly.

This package uses Go modules.

To see the list of past versions, run git tag.

Contributing

We love pull requests! Please see the contribution guidelines.

License

This library is distributed under the BSD-style license found in the LICENSE file.

Documentation

Overview

Example (StructuredAttestation)

Attest a road trip with a structured schema.

package main

import (
	"context"
	"log"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"resenje.org/eas"
)

var (
	endpointSepolia        = "https://ethereum-sepolia-rpc.publicnode.com/"
	contractAddressSepolia = common.HexToAddress("0xC2679fBD37d54388Ce493F1DB75320D236e1815e")
)

type RoadTrip struct {
	ID           uint64      `abi:"id"`
	VIN          string      `abi:"vin"`
	VehicleOwner string      `abi:"vehicleOwner"`
	Passengers   []Passenger `abi:"passengers"`
}

type Passenger struct {
	Name     string `abi:"name"`
	CanDrive bool   `abi:"canDrive"`
}

func (p Passenger) CanDriveString() string {
	if p.CanDrive {
		return "can drive"
	}
	return "cannot drive"
}

func main() {
	ctx := context.Background()

	// Use a fake key here. Use your own funded key to be able to send transactions.
	privateKey, err := eas.HexParsePrivateKey("a896e1f28a6453e8db4794f11ea185befd04c4e4f06790e37e8d1cc90a611948")
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Wallet address:", crypto.PubkeyToAddress(privateKey.PublicKey))

	// Construct a client that will interact with EAS contracts.
	c, err := eas.NewClient(ctx, endpointSepolia, privateKey, contractAddressSepolia, nil)
	if err != nil {
		log.Fatal(err)
	}

	// Create the Schema on chain.
	tx, waitRegistration, err := c.SchemaRegistry.Register(ctx, eas.MustNewSchema(RoadTrip{}), common.Address{}, true)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Waiting schema registration transaction:", tx.Hash())
	schemaRegistration, err := waitRegistration(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Just check the schema definition.
	schema, err := c.SchemaRegistry.GetSchema(ctx, schemaRegistration.UID)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Schema UID:", schema.UID)
	log.Println("Schema:", schema.Schema)

	// Attest a road trip on chain.
	tx, waitAttestation, err := c.EAS.Attest(ctx,
		schema.UID,
		&eas.AttestOptions{Revocable: true},
		RoadTrip{
			ID:           4242,
			VIN:          "1FA6P8CF5L5100421",
			VehicleOwner: "Richard Hammond",
			Passengers: []Passenger{
				{
					Name:     "James May",
					CanDrive: true,
				},
				{
					Name:     "Jeremy Clarkson",
					CanDrive: true,
				},
				{
					Name:     "The Stig",
					CanDrive: false,
				},
			},
		},
	)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("Waiting attest transaction:", tx.Hash())
	attestConfirmation, err := waitAttestation(ctx)
	if err != nil {
		log.Fatal(err)
	}

	// Get the attestation to verify it.
	a, err := c.EAS.GetAttestation(ctx, attestConfirmation.UID)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Attestation UID", a.UID)
	log.Println("Attestation Time", a.Time)

	var roadTrip RoadTrip
	if err := a.ScanValues(&roadTrip); err != nil {
		log.Fatal(err)
	}

	log.Println("Road trip:", roadTrip.ID)
	log.Println("Vehicle Identification Number:", roadTrip.VIN)
	log.Println("Vehicle owner:", roadTrip.VehicleOwner)
	for i, p := range roadTrip.Passengers {
		log.Printf("Passenger %v: %s (%s)", i, p.Name, p.CanDriveString())
	}
}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func HexParsePrivateKey

func HexParsePrivateKey(h string) (*ecdsa.PrivateKey, error)

func LoadEthereumKeyFile

func LoadEthereumKeyFile(fs fs.FS, filename, auth string) (*ecdsa.PrivateKey, error)

func MustNewSchema

func MustNewSchema(args ...any) string

func NewSchema

func NewSchema(args ...any) (string, error)

func Ptr

func Ptr[T any](v T) *T

Types

type AttestOptions

type AttestOptions struct {
	Recipient      common.Address
	ExpirationTime time.Time
	Revocable      bool
	RefUID         UID
	Value          *big.Int
}

type Attestation

type Attestation struct {
	UID            UID
	Schema         UID
	Time           time.Time
	ExpirationTime time.Time
	RevocationTime time.Time
	RefUID         UID
	Recipient      common.Address
	Attester       common.Address
	Revocable      bool
	Data           []byte
}

func (Attestation) IsRevoked

func (a Attestation) IsRevoked() bool

func (Attestation) ScanValues

func (a Attestation) ScanValues(fields ...any) error

type Backend

type Backend interface {
	bind.ContractBackend
	bind.DeployBackend
	ethereum.ChainIDReader
}

type Client

type Client struct {

	// Contracts
	SchemaRegistry *SchemaRegistryContract
	EAS            *EASContract
	// contains filtered or unexported fields
}

func NewClient

func NewClient(ctx context.Context, endpoint string, pk *ecdsa.PrivateKey, easContractAddress common.Address, o *Options) (*Client, error)

func (*Client) Address

func (c *Client) Address() common.Address

func (*Client) Backend

func (c *Client) Backend() Backend

type ContractError

type ContractError struct {
	Name      string
	Arguments []ContractErrorArgument
}

func (*ContractError) Error

func (e *ContractError) Error() string

type ContractErrorArgument

type ContractErrorArgument struct {
	Name  string
	Type  string
	Value any
}

type EASAttested

type EASAttested struct {
	Recipient common.Address
	Attester  common.Address
	UID       UID
	Schema    UID
	Raw       types.Log
}

type EASContract

type EASContract struct {
	// contains filtered or unexported fields
}

func (*EASContract) Attest

func (c *EASContract) Attest(ctx context.Context, schemaUID UID, o *AttestOptions, values ...any) (*types.Transaction, WaitTx[EASAttested], error)

func (*EASContract) FilterAttested

func (c *EASContract) FilterAttested(ctx context.Context, start uint64, end *uint64, recipient []common.Address, attester []common.Address, schema []UID) (Iterator[EASAttested], error)

func (*EASContract) FilterRevoked

func (c *EASContract) FilterRevoked(ctx context.Context, start uint64, end *uint64, recipient []common.Address, attester []common.Address, schema []UID) (Iterator[EASRevoked], error)

func (*EASContract) FilterRevokedOffchain

func (c *EASContract) FilterRevokedOffchain(ctx context.Context, start uint64, end *uint64, revoker []common.Address, data []UID, timestamp []uint64) (Iterator[EASRevokedOffchain], error)

func (*EASContract) FilterTimestamped

func (c *EASContract) FilterTimestamped(ctx context.Context, start uint64, end *uint64, data []UID, timestamps []Timestamp) (Iterator[EASTimestamped], error)

func (*EASContract) GetAttestation

func (c *EASContract) GetAttestation(ctx context.Context, uid UID) (*Attestation, error)
Example
package main

import (
	"context"
	"log"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/crypto"
	"resenje.org/eas"
)

var (
	endpointSepolia        = "https://ethereum-sepolia-rpc.publicnode.com/"
	contractAddressSepolia = common.HexToAddress("0xC2679fBD37d54388Ce493F1DB75320D236e1815e")
)

func main() {
	ctx := context.Background()

	privateKey, err := crypto.GenerateKey()
	if err != nil {
		log.Fatal(err)
	}

	c, err := eas.NewClient(ctx, endpointSepolia, privateKey, contractAddressSepolia, nil)
	if err != nil {
		log.Fatal(err)
	}

	attestationUID := eas.HexDecodeUID("0xac812932f5cee90a457d57a9fbd7b142b21ba99b809f982bbf86947f295281ff")

	a, err := c.EAS.GetAttestation(ctx, attestationUID)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("Attestation UID", a.UID)
	log.Println("Attestation Time", a.Time)

	var schemaUID eas.UID
	var name string

	if err := a.ScanValues(&schemaUID, &name); err != nil {
		log.Fatal(err)
	}

	log.Println("Attestation")
	log.Println("Schema UID:", schemaUID)
	log.Println("Name:", name)
}
Output:

func (*EASContract) GetRevokeOffchain

func (c *EASContract) GetRevokeOffchain(ctx context.Context, revoker common.Address, uid UID) (uint64, error)

func (*EASContract) GetTimestamp

func (c *EASContract) GetTimestamp(ctx context.Context, data UID) (Timestamp, error)

func (*EASContract) IsAttestationValid

func (c *EASContract) IsAttestationValid(ctx context.Context, uid UID) (bool, error)

func (*EASContract) MultiAttest

func (c *EASContract) MultiAttest(ctx context.Context, schemaUID UID, o *AttestOptions, attestations ...[]any) (*types.Transaction, WaitTxMulti[EASAttested], error)

func (*EASContract) MultiRevoke

func (c *EASContract) MultiRevoke(ctx context.Context, schemaUID UID, attestationUIDs []UID) (*types.Transaction, WaitTxMulti[EASRevoked], error)

func (*EASContract) MultiRevokeOffchain

func (c *EASContract) MultiRevokeOffchain(ctx context.Context, schemaUID UID, uids []UID) (*types.Transaction, WaitTxMulti[EASRevokedOffchain], error)

func (*EASContract) MultiTimestamp

func (c *EASContract) MultiTimestamp(ctx context.Context, data []UID) (*types.Transaction, WaitTxMulti[EASTimestamped], error)

func (*EASContract) Revoke

func (c *EASContract) Revoke(ctx context.Context, schemaUID, attestationUID UID, o *RevokeOptions) (*types.Transaction, WaitTx[EASRevoked], error)

func (*EASContract) RevokeOffchain

func (c *EASContract) RevokeOffchain(ctx context.Context, uid UID) (*types.Transaction, WaitTx[EASRevokedOffchain], error)

func (*EASContract) Timestamp

func (c *EASContract) Timestamp(ctx context.Context, data UID) (*types.Transaction, WaitTx[EASTimestamped], error)

func (*EASContract) Version

func (c *EASContract) Version(ctx context.Context) (string, error)

func (*EASContract) WatchAttested

func (c *EASContract) WatchAttested(ctx context.Context, start *uint64, sink chan<- *EASAttested, recipient []common.Address, attester []common.Address, schema []UID) (event.Subscription, error)

func (*EASContract) WatchRevoked

func (c *EASContract) WatchRevoked(ctx context.Context, start *uint64, sink chan<- *EASRevoked, recipient []common.Address, attester []common.Address, schema []UID) (event.Subscription, error)

func (*EASContract) WatchRevokedOffchain

func (c *EASContract) WatchRevokedOffchain(ctx context.Context, start *uint64, sink chan<- *EASRevokedOffchain, revoker []common.Address, data []UID, timestamp []uint64) (event.Subscription, error)

func (*EASContract) WatchTimestamped

func (c *EASContract) WatchTimestamped(ctx context.Context, start *uint64, sink chan<- *EASTimestamped, data []UID, timestamps []Timestamp) (event.Subscription, error)

type EASRevoked

type EASRevoked struct {
	Recipient common.Address
	Attester  common.Address
	UID       UID
	Schema    UID
	Raw       types.Log
}

type EASRevokedOffchain

type EASRevokedOffchain struct {
	Revoker   common.Address
	Data      UID
	Timestamp time.Time
	Raw       types.Log
}

type EASTimestamped

type EASTimestamped struct {
	Data      UID
	Timestamp Timestamp
	Raw       types.Log
}

type Error

type Error struct {
	StatusCode int
	Code       int
	Message    string
}

func (*Error) Error

func (e *Error) Error() string

type Iterator

type Iterator[T any] interface {
	Value() T
	Close() error
	Error() error
	Next() bool
}

type Options

type Options struct {
	SchemaRegistryContractAddress common.Address
	GasLimit                      uint64
	GasFeeCap                     *big.Int
	GasTipCap                     *big.Int
	Backend                       Backend
}

type RevokeOptions

type RevokeOptions struct {
	Value *big.Int
}

type SchemaRecord

type SchemaRecord struct {
	UID       UID
	Resolver  common.Address
	Revocable bool
	Schema    string
}

type SchemaRegistryContract

type SchemaRegistryContract struct {
	// contains filtered or unexported fields
}

func (*SchemaRegistryContract) FilterRegistered

func (c *SchemaRegistryContract) FilterRegistered(ctx context.Context, start uint64, end *uint64, uids []UID) (Iterator[*SchemaRegistryRegistered], error)

func (*SchemaRegistryContract) GetSchema

func (c *SchemaRegistryContract) GetSchema(ctx context.Context, uid UID) (*SchemaRecord, error)

func (*SchemaRegistryContract) Register

func (*SchemaRegistryContract) Version

func (c *SchemaRegistryContract) Version(ctx context.Context) (string, error)

func (*SchemaRegistryContract) WatchRegistered

func (c *SchemaRegistryContract) WatchRegistered(ctx context.Context, start *uint64, sink chan<- *SchemaRegistryRegistered, uids []UID) (event.Subscription, error)

type SchemaRegistryRegistered

type SchemaRegistryRegistered struct {
	UID        UID
	Registerer common.Address
	Raw        types.Log
}

type Timestamp

type Timestamp uint64

func (Timestamp) Time

func (t Timestamp) Time() time.Time

type UID

type UID [32]byte

func HexDecodeUID

func HexDecodeUID(s string) UID

func (UID) IsZero

func (u UID) IsZero() bool

func (UID) MarshalText

func (u UID) MarshalText() ([]byte, error)

func (UID) String

func (u UID) String() string

func (*UID) UnmarshalText

func (u *UID) UnmarshalText(b []byte) error

type WaitTx

type WaitTx[T any] func(ctx context.Context) (*T, error)

type WaitTxMulti

type WaitTxMulti[T any] func(ctx context.Context) ([]T, error)

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL