GithubHelp home page GithubHelp logo

perun-network / erdstall-ts-sdk Goto Github PK

View Code? Open in Web Editor NEW
5.0 5.0 2.0 4.86 MB

TypeScript client SDK to interact with Erdstall.

License: Apache License 2.0

Makefile 0.07% TypeScript 99.59% Shell 0.34%
ethereum plasma rollup scaling sdk typescript

erdstall-ts-sdk's People

Contributors

rmbrt avatar ruben-kruepper avatar sebastianst avatar

Stargazers

 avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

erdstall-ts-sdk's Issues

SDK adjustments

  • Adapt subscribe() call to new backend implementation:
    • subscribe("balanceProof", filterArgs)
    • subscribe("txs")
    • Also add a way to subscribe to both in a single call.
    • BalanceProofs have to be adapted to new backend implementation, see erdstall-operator.
  • Add subscribe() to public Erdstall interface:
    • SDK would hang up if a user connects to Erdstall without subscribing, because it expects to receive TxReceipts.
    • Erdstall backend would need to pack TxReceipts in a Result call.
  • Add convenience constructors for LedgerConnection and EnclaveConnection:
    • Some users might not want to directly participate in Erdstall by issuing transactions, but just want to listen to events happening.
  • Move EnclaveConnection interface into erdstall.ts
  • Move the subscribe() interface method into some interface, this might be EnclaveWatcher or a new Subscriber.
  • Expose common namespace in the top-level index.ts, to ease importing from the SDK.

Change all (complex) relative imports to use package anchor

In #18, a package anchor #erdstall in the form of an internal imports specification in package.json was created. The corresponding paths translation was also added to tsconfig.json and all files in src/test were updated to already use this new form of import.

I propose that we change all (complex) imports, that is, not short ones like import * from './ledger' in immediately surrounding files/dirs to use the package anchor.

We could also add a second #test import spec to package.json:imports that points to src/test and also then finally remove the test->src/test symlink. Of course, we could also just use #erdstall/test but since it's just an internal reference, we can spare a second one :)

To complete this issue, also remove the dev dependency tsconfig-paths and its mocha registration in the test script and from the tsconfig.*.json files.

Make `subscribe` aware of set handlers

subscribe currently subscribes to both, receipts and balance proofs. It should only subscribe to what handlers are set for. For the time being, we don't require it to be smart enough to add subscriptions if any handler is set after a subscribe call. It is sufficient that, if a user adds a new handler, they have to call subscribe again. This should also be documented in the API.

Use yarn in CI

It still uses npm. Can also enable the caching option of the node setup action.

Change addresses to lower-case when JSON marshalling `Values`

We found out that the standard format for addresses in JSONs use all-lower-case 0x prefixed strings, not the checksum strings. So this has to be changed when marshalling addresses, in particular maps that use addresses as keys, like Values.

As part of this issue, it should be made sure that Values keys are always in lower-case format.

This also has to be changed in the Erdstall operator (https://github.com/perun-network/erdstall-ext/issues/96).

`target` property issue?

Looking at this and the fact we have es2020 as a target set in our tsconfig.json, we expect no problems.

However, including our lib in front end applications throws an error, in which bigint ** bigint falsely throws an Uncaught TypeError: Cannot convert a BigInt value to a number.
Exponentiation like this is clearly supported as can be seen here, so we either have a problem with our project setup or this is a bug.

Further investigation needed. For the time being, we can just replace 2n ** 256n - 1n with the following:

const prettyNumber = 1n << 256n;
export const MAX_AMOUNT_VALUE = prettyNumber - 1n;

Using a temporary variable because prettier does not allow to use ( ) here to ensure correct operator precedence...

Overhaul `Stages` and use `AsyncIterators` where applicable

We currently have abominations like the following in our API:

	async leave(): Promise<Stages<Promise<ethers.ContractTransaction>>> {

It would be very convenient to:

  1. reduce the complexity of the return type and make it more expressive
  2. give the caller a way to know with how many stages he will be working with (if possible)

This would result in the following:

	async leave(): AsyncIterator<SomeType> {

or similar.

For this we could probably use the default interface of an AsyncIterator and extend it slightly with an optional length?: number value. it.length would then, if not undefined, allow a developer to display a proper progressbar in a frontend application, while the fact that an Iterator is returned perfectly conveys the fact, that multiple transactions are being awaited.

The intricacies of the implementation, whether the Stages helper is really necessary or just has to implement the AsyncIterator interface is up for the implementer to research and decide.

Make `TxReceipt` polymorphic

Currently this is required:

		const tx = await session.mint(nftAddress, nftId);
		const minttx = tx.tx as Mint;

I think it would be nice to have a polymorphic TxReceipt<T extends Transaction> helper, which types the internal transaction:

		const minttx: TxReceipt<Mint> = await session.mint(nftAddress, nftId);

This can then be set in the return values of our interfaces and should be straight forward to implement.

Fix naive `NewUintXXX` implementation.

Current implementation has a big problem ๐Ÿคก

// NewBigInt generates a random `BigInt` in range 0 <= `CEIL`.
export function NewBigInt(rng: PRNG, CEIL: bigint): bigint {
	const base = CEIL / 2n;
	const offset = (BigInt(Math.floor(100 * rng.uFloat32())) * base) / 100n; // <-- Not good.
	const add = (v1: bigint, v2: bigint): bigint => {
		return v1 + v2;
	};
	const sub = (v1: bigint, v2: bigint): bigint => {
		return v1 - v2;
	};
	const op = rng.uFloat32() < 0.5 ? add : sub;
	return op(base, offset);
}

This results in a lot of repeated values and false negatives in some tests. Even better if we can find a battletested library which we could utilise for stuff like this.

Expose on-chain `WithdrawalException` event on Erdstall Client API

If an individual asset's withdrawal throws during a withdraw on-chain call, the Erdstall smart contract doesn't revert the whole transaction, but just throws a WithdrawalException for this particular asset. This is a safety mechanism to avoid a single token from reverting other token withdrawals indefinitely.

This event still needs to be exposed on the Erdstall client API.

When testing this, the RevertToken from can be used to trigger a revert during a withdrawal.

Detect when a call is made to an uninitialized connection

Track whether a connection is already fully initialized or disconnected and throw an error that details this condition. Currently, it throws a DOMException: An attempt was made to use an object that is not, or is no longer, usable, which is completely useless for debugging.

Extend `PRNG` to log name of the attached testsuite

Currently our PRNG allows us to replay our tests because it logs the seed, but we are now at a point, where filtering the seed out of the testlog is rather cumbersome.

We should extend the test.newPrng() to log the testsuite it is part of. mocha gives some abilities to inspect the running test and maybe there is even a fail/error callback we could use to add our testseed?

Whatever route we take, this has to be changed.

CI: Run end to end test

Currently, the end-to-end-test does not run in the CI. Would be nice to have it, especially because it would've caught a bug after the implementation of typed-handlers.

NFT Metadata Provider/Querier

We should add means to the client to query nft metadata. OpenSea has docs on metadata standards.

The interfaces could look like

interface NFTMetadata {
  name: string;
  description: string;
  image: string; // image URL
  attributes?: []Attribute;
  background_color?: string; // six-char hex
 // ... more optional fields?
}

interface Attribute {
  // please complete with OpenSea metadata standards fields
}

interface NFTMetadataProvider {
  getNftMetadata(token: Address, id: bigint): Promise<NFTMetadata>;
}

For generic NFTs that exist on-chain, the implementation should

  1. query the tokenURI on-chain (and cache it for future requests)
  2. query the metadata on this URI

In the meantime, we should adapt the backend server to conform to this standard (https://github.com/perun-network/nerd-marketplace/issues/100) so we can use the generic implementation also for our PerunArt example contract.

The frontend should then use this functionality from the SDK to display any NFT.

Expose `Provider` from `ErdstallClient/ErdstallSession`

The ErdstallClient and ErdstallSession require a provider to be able to communicate with the underlying ledger. From a user perspective it would be nice to have the Provider exposed.

Example:

I am currently a user of this SDK and heavily use the ErdstallSession in my code. For 9 out of 10 things it does the job I need and gives a way to access whatever I need. But the tenth thing is connecting to a Contract on the ledger. To make this work, I have to use a Provider and it would be nice to just be able to retrieve that Provider form the Session I use everywhere else. Otherwise I am forced to manually track this provider, which is possible but not feels awkward, when it is right in front of me but locked behind a protected directive.

Any thoughts?

Test ERC721 deposit

In file ledger/backend/contracts_deposit.spec.ts currently only ETH and ERC20 deposits are tested.

Extend Token Querier with filter

We want to query on-chain registered contracts for different token types with the client. I propose the following function signature on the interface LedgerReader.

interface TokenRegistration {
  address: Address, // token contract address
  type: string // token type "ETH" | "ERC20" | "ERC721"
  holder: Address // token holder address, where deposits can be made to
  symbol?: string // optional token symbol, if contract supports symbol() call
}

LedgerReader.getRegisteredTokens(filter?: { tokenType?: string, tokenSymbol?:string}): TokenRegistration[]

Note that token contracts not necessarily support the symbol() string call, only if they implement ERC20Detailed.

A default implementation should be added to the Client. The call would query all on-chain registered tokens by filtering to the TokenRegistered events. If the optional filter is given, it would filter to only those registrations that match the specified tokenType or tokenSymbol.

Custom use of `TypedJSON` on client side not working.

When a client, who is using our SDK, would try to go low level and implement some custom EnclaveProvider TypedJSON seems not to be initialised correctly.

Calls like TypedJSON.parse(<my-event-json>, sdk.api.result.Result) do not return the expected Result type indicating that TypedJSON does not call our defined en-/decoding handlers.

However, using the Enclave itself works fine.

Add `.equals(...)` methods to Erdstall base types

To aid the intuition of developers using our SDK we should strive to use a familiar API for our types.

{Asset|Assets} includes a cmp(...) method, while types like Address and BigInteger implement an equals method.

I propose to add the equals method where possible. Currently that would be in {Asset|Assets}.

See also here.

Allow NFT deposits

For some reason, depositing still only works with fungibles, it seems. At least I got that error when testing my deposit dialog.

Change return value of `Asset.typeTag(): string` to an exhaustively checkable type at compile time

Currently TypeTags is defined as an interface with the keys Amount and Tokens.

The corresponding Asset.typeTag() function returns a simple string. It would be beneficial to constrain the return type of Asset.typeTag() to be a sum type of the possible typeTags, currently "uint" | "idset".

This sum type should be compiler derived from the TypeTags type, which limits the number of changes necessary until the compiler can throw all necessary errors to make refactoring a simple task of following these errors and extend implementation. In the future all we would have to do is extend the TypeTags type and the rest gets derived on its own from tsc.

This would allow users to exhaustively switch over the Asset.typeTag() function enabling more rigorous implementations and gives confidence when updating the SDK:

	const resolveAssetAmount = (a: Asset): TypeTagsValue => {
		switch (asset.typeTag()) {
			case TypeTags.Amount:
				return utils.formatEther((a as Amount).value);
			case TypeTags.Tokens:
				return (a as Tokens).value.length.toString();
			// no default case. User gets compile time error here when an
			// updated SDK version has extended the `TypeTags` type.
		}
	};

Users are forced to use a default case currently because asset.typeTag() returns a string. Any update to the SDK will not lead to an error here and just fails at runtime, which is inconvenient.

Investigate error reporting and checking without losing stacktraces

As mentioned in #69 (comment), we are not really sure how to properly expose errors thrown by our SDK.

IMO, it would be ideal if we could allow users to do the following:

try {
    // some awesome SDK functions/methods used.
} catch (e) {
    switch (sdk.asErdstallError(e)) {
        case "Connection": // Handle connection error.
        case "Signature": // Handle signature error.
    }
    // Standard way to handle errors.
}

I realise this is opinionated, but I really dislike the way errors have to be checked in "default" TS. Finding a trusted and battletested library which could be used to take some burden off of the user and guiding them to properly handle exceptions/errors is beneficial in the long run.

Pass epoch and block number in phase shift event

A phase shift handler currently only receives a signal without any information. We could pass the old epoch number and block number to the handler as an

interface PhaseShiftEvent {
  epoch: number; // just passed epoch
  blockNumber: number;
}

Split Erdstall and Ledger Client into Client and Session

The LedgerConnection, EnclaveConnection and Erdstall interfaces currently mix up read-only operations (state reads and event subscriptions) and write operations (sending of transactions). This is problematic because a read-only user like a marketplace only needs the read parts. Also, the implementations need signers, which a read-only actor doesn't have and shouldn't need to provide.

So we split up the interfaces and call the read-only part Client/Reader and the extended write part Session/Writer.
Interface Erdstall from erdstall.ts should become ErdstallSession. It extends ErdstallClient, which only extends the read-only interfaces. The Enclave and Ledger connections are split into Reader and Writer parts.

Full interface extension tree

The following tree shows how interfaces extend (embed) each other, and mentions some methods explicitly.

ErdstallSession

  • ErdstallClient

    • LedgerReader
      • ErdstallWatcher (I'd prefer to rename to LedgerWatcher so that prefix Erdstall stands for combined stuff. Same for LedgerEvent etc.)
      • erdstall(): Address
    • EnclaveReader
      • EnclaveWatcher
      • getAccount
  • LedgerWriter

    • Depositor
    • Withdrawer
  • EnclaveWriter

    • Onboarder (new interface, contains method onboard)
    • Transactor
    • Minter
    • Exiter
    • Leaver
  • I'd also add an interface Connector that has the two methods connect and disconnect and extend it in interfaces that need it.

  • I'd move method subscribe into the watcher interface because on, once, off go together with subscribe, if I understand it correctly.

Implementations

The implementations also need to be split up, so that we can have a read-only Client that implements the ErdstallClient interface and a writing, user-bound ErdstallSession that implements the Session interface.

Remove package `tsconfig-paths` and its registrations

Since we use 1. #erdstall via an imports config in package.json and 2. a paths translation in tsconfig, we should be able to remove the package tsconfig-paths and also all registrations, e.g. in the mocha test scripts (-r tsconfig-paths/register). Note that we also translate paths in the output transpilate using ttsc.

I was able to remove it in nerd-marketplace as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.