Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add package for client sdk #45

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

jtary
Copy link
Contributor

@jtary jtary commented Jun 7, 2024

Adds a lightweight library people can use for interacting with the new delegation features.

packages/relay-sdk/src/index.ts Outdated Show resolved Hide resolved
packages/relay-sdk/src/index.ts Outdated Show resolved Hide resolved
packages/relay-sdk/src/index.ts Outdated Show resolved Hide resolved
"esbuild": "^0.21.4"
},
"dependencies": {
"@lit-protocol/constants": "^6.0.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we using 6.0.0 and not 6.0.1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, not sure why it decided to go with that. Functionally, they should be the same, since it will grab the most recent compatible version, but I can bump the patch version just to be explicit.

})
.then(assertStatus(200, 'Failed to add payee: request failed'))
.then(res => res.json())
.then(json => assertString(json.tokenId, 'Failed to add payee: missing token ID'))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should explicitly type these endpoints in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not quite sure what you mean, what 'endpoints' need explicit typing?

import { RELAY_URL_HABANERO, RELAY_URL_MANZANO } from '@lit-protocol/constants';
import { assertNotNull, assertStatus, assertString, getProp } from './util';

type SupportedNetworks = 'habanero' | 'manzano';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use LIT_NETWORKS for this instead of our own string values

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there some way to filter the networks by supported features? I looked into this, but I would have ended up needing to hard code a union anyways since I need to filter out the other networks.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we need to define networks w/ specific behaviours, it'd be great to implement those maps in our sdk's constants similarly to how we define some things in lit-core here:
https://github.com/LIT-Protocol/js-sdk/blob/d8c7dc55febb262154061aaac01daad4aae1b239/packages/core/src/lib/lit-core.ts#L105-L117

This is related to my above question -- why not have this package live in the js-sdk? Then we could have single PRs that include updates to constants or other packages and this code as well. A good example of this in action is that we just refactored how we select relayer URLs in the SDK; this code would've been updated as a matter-of-course if it was in the same repo. See PR

Copy link

@MaximusHaximus MaximusHaximus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the long delay in reviewing this -- I've added feedback/comments in-line :)

throw new Error('Failed to register payer: missing secret key');
}

return new LitRelayClient(baseUrl, apiKey, data.payerSecretKey);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

payerSecret is private (so shouldn't be referenced), and there is no getter to expose it... but consumers of this client will need that secret later on in order to add more payees -- and likely they will need to do so at some point in the future when this instance has gone out of scope. My understanding is that they will need to call register() somewhere and then stash that secret somewhere for future use.

If that's true, rather than create a new instance with the secret injected, can we just return the secret to the caller so that they can either store it for later use or use it to create a new instance? Even better, they could call setPayerSecretKey(payerSecretKey) on the instance that they already have rather than having 2 instances in scope -- one with only the apiKey and another with both the apiKey and the payerSecretKey.

import { LitRelayClient } from '@lit-protocol/relay-sdk';

const client = await LitRelayClient.register('habanero', 'you-api-key');
const secret = client.secret;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property was renamed since this was written, I think?

## Installation

```bash
npm install @lit-protocol/relay-sdk

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this package live in the js-sdk repository? It has dependencies on many packages there -- we're going to need to keep it up-to-date with the SDK, and our release process for publishing NPM packages is already defined there and for publishing the relay server to our hosting is already defined here but no client -- this is even listed as an sdk package, and we even have a relayer object defined in the js sdk in the lit-auth-client package.

private readonly baseUrl: string;
private readonly apiKey: string;

private payerSecret: string | undefined = undefined;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:nit: can just be ? optional :)

Suggested change
private payerSecret: string | undefined = undefined;
private payerSecret?: string

* @param apiKey
* @param payerSecret
*/
private constructor(baseUrl: string, apiKey: string, payerSecret: string) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wild -- a constructor can actually be private?! I learned something new today. if this is actually true though, why make it private? Why not allow someone to construct an instance of the object?

*/
public addPayee(payeeAddress: string): Promise<{ tokenId: string }> {
if (!this.payerSecret) {
return Promise.reject('Payer secret key is missing');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other comment -- async this method and always throw an Error instance please 💖

*
* @returns Promise<{ tokenId: string } | Error>
*/
public addPayee(payeeAddress: string): Promise<{ tokenId: string }> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we change this function to be addPayees given that the backend API supports that? Otherwise we will inevitably end up with 2 methods, and this method will end up either calling the addPayees method or duplicating the code inside of that method.

@@ -0,0 +1,25 @@
export function assertStatus(code: number, error: string) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update assertStatus so that rather than ignoring the response entirely in cases where its code doesn't match what we expected, we instead inspect it and include the actual reason why the request fails if the response included it? It would be preferable to inspect the response and only provide a generic string back to the caller if there was nothing returned by the backend service to pass along to the user. There's an example of this pattern here

return value;
}

export function assertString(value: any, error: string) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:nit: This could be a TS assertion function instead of using as on the return

Suggested change
export function assertString(value: any, error: string) {
export function assertString(value: any, error: string) asserts value is string {

Ref: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#:~:text=The%20other%20type%20of%20assertion,property%20has%20a%20different%20type.&text=Here%20asserts%20val%20is%20string,known%20to%20be%20a%20string%20.&text=assertIsString(str)%3B

* @returns LitRelayClient
*/
public static connect(network: SupportedNetworks, apiKey: string, payerSecret: string) {
if (network !== 'habanero' && network !== 'manzano') {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we run this check in all methods, it'd be nice if it was standardized to a single assert function


const data = await res.json();

if (!data.payerSecretKey) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the API is going to return payerSecretKey, can we name it the same thing on this object? It'd be nice for the same magic strings to be used in both the client and server contexts for easier discoverability

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants