npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@grexie/signable

v0.1.6

Published

Inheritable solidity smart contract, ABIs and Node.js library to sign Web3 requests to a Signable contract.

Downloads

12

Readme

Grexie Signable

Inheritable solidity smart contract, ABIs and Node.js library to sign Web3 requests to a Signable contract.

Installing

yarn add @grexie/signable

Usage

This repository provides a solidity abstract contract that allows solidity methods to authorize transactions based on a secure signature generated from a known signer address, controlled by a backend such as Node.js on AWS.

The signer account private key can be stored in Grexie Vault or AWS Secrets Manager and consumed from AWS Lambda, EKS, ECS or EC2 and similar services from Google, etc. The suggestion is to rotate the signer account manually and regularly, and update the signer account from a signed method such as setSigner in the smart contract.

It is also possible to delegate the signer to a registry smart contract, so that all sub-contracts can use the same signer.

Each instance of the Signable contract generates its own uniq value, which cannot be changed for the lifetime of the contract. The uniq value protects against replay attacks across multiple instances of the same contract, on different chains or on the same chain. The uniq value is the keccak256 hash of block.timestamp and address(this).

To implement the ISignable interface one should create a smart contract such as the following:

pragma solidity ^0.8.0;

import '@grexie/signable/contracts/Signable.sol';

contract MySignableContract is Signable {
  address private _signer;

  constructor(address signer_) {
    _signer = signer_;
  }

  function signer() public view virtual override(ISignable) returns (address) {
    return _signer;
  }

  function setSigner(address signer_, Signature calldata signature)
    external
    verifySignature(
      abi.encode(this.setSigner.selector, signer_),
      signature
    )
  {
    _signer = signer_;
  }
}

Then to call setSigner you would instantiate the Signer class in Node.js in a secure server environment and call the Signer#sign method. Note that this needs to be authenticated such that only users you want to have access to signer rotation can access this method. You need to plan carefully access to any signed method, and access to the private key.

import ISignable from '@grexie/signable/contracts/ISignable.json';

const signer = Signer({
  keyStore: {
    async get(signerAddress: string): string {
      return MyKeyStore.get(signerAddress);
    },
  },
  address: process.env.CONTRACT_ADDRESS,
  web3,
});

const signature = await signer.sign(
  ISignable,
  'setSigner',
  userAddress,
  newSignerAddress
);

Then on the front end you can execute a gasless (user pays the gas fees) transaction for this method:

const contract = new web3.eth.Contract(ISignable, process.env.CONTRACT_ADDRESS);

await contract.methods
  .setSigner(newSignerAddress, signature)
  .send({ from: userAddress });

Likewise you can secure any other method, including those using structs. Check out Example.sol in GitHub and the signable.spec.js for details of how this works and the test coverage.

The Signer Node.js class expects verifySignature in the contract to implement all method parameters, in order, except for the Signature parameter. You can therefore place the Signable.Signature argument anywhere in the method signature, and it will simply be filtered out by the Signer class when signing. We recommend to place it at the end of the argument list though for simplicity.

For example:

struct MyStruct2 {
  string field3;
  string field4;
  string field5;
}

struct MyStruct1 {
  string field1;
  MyStruct2 field2;
}

function myMethodA(string calldata arg1, MyStruct1 calldata arg2, Signature calldata signature)
    external
    verifySignature(
      abi.encode(this.myMethodA.selector, arg1, arg2),
      signature
    )
  {
    ...
  }

The corresponding Node.js code:

const args = [
  'arg1',
  {
    field1: 'value1',
    field2: {
      field3: 'value3',
      field4: 'value4',
      field5: 'value5',
    },
  },
];

const signature = await signer.sign(
  MyContractABI,
  'myMethodA',
  userAddress,
  ...args
);

And the front end code:

const contract = new web3.eth.Contract(
  MyContractABI,
  process.env.CONTRACT_ADDRESS
);

await contract.methods
  .myMethodA(...args, signature)
  .send({ from: userAddress });

The Signer Node.js class implements provision for an optional cache such as Redis. Pass in the cache option on construction and implement the get and set methods. The cache stores the uniq value for a contract indefinately and the signer value for a contract for 1 hour. Typically you'd implement the cache interface as follows:

const signer = new Signer({
  ...
  cache: {
    async get(key: string): Promise<string> {
      const value = await redis.get(key);
      if (!value) {
        return null;
      } else {
        return JSON.parse(value);
      }
    },
    async set(key: string, value: string, ttl: number): Promise<void> {
      await redis.setex(key, ttl, JSON.stringify(value));
    }
  }
});

const onRotate = async () => {
  await redis.del(Signer.SignerCacheKey(contractAddress));
};