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

Feat/dApp signer enhancement #76

Closed

Conversation

franfernandez20
Copy link
Contributor

Description:
General improvements to the DAppConnector. The enhancements cover various aspects of the connector, supporting several sessions, and facilitating the use of the client from the dApps side.

  • Add methods to directly use transaction
  • Support multiple sessions
  • Remove unnecessary code
  • Enhance certain performance aspects.

Related issue(s):

Fixes #

Notes for reviewer:

Checklist

  • Documented (Code comments, README, etc.)
  • Tested (unit, integration, etc.)

franfernandez20 and others added 3 commits March 9, 2024 02:20
This commit includes general improvements to the DAppConnector.
The enhancements cover various aspects of the connector, supporting
several sessions, and facilitate the use of the client from the dApps
side.

Signed-off-by: Fran Fernandez <fran@kabila.app>
Signed-off-by: Fran Fernandez <fran@kabila.app>
Signed-off-by: Fran Fernandez <fran@kabila.app>
@franfernandez20 franfernandez20 changed the title Feat/d app signer enhancement Feat/dApp signer enhancement Mar 9, 2024
README.md Outdated Show resolved Hide resolved
Signed-off-by: Fran Fernandez <fran@kabila.app>
@franfernandez20
Copy link
Contributor Author

Hi @mgarbs, can I kindly request to have another reviewer assigned to this PR?

Copy link

@justynspooner justynspooner left a comment

Choose a reason for hiding this comment

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

Nice work 👍 Looks like a good improvement for simplifying the setup for dapps.

The main thing that jumps out is the removal of the logic to test for active connections around persisted sessions.

Happy to look at approving once those are reviewed.

Thanks!

Comment on lines -334 to -350
new Promise(async (resolve, reject) => {
try {
await this.pingWithRetry(session.topic)
resolve(session)
} catch (error) {
try {
await this.walletConnectClient!.disconnect({
topic: session.topic,
reason: getSdkError('SESSION_SETTLEMENT_FAILED'),
})
reject(`Ping failed, disconnecting from session. Topic: ${session.topic}`)
} catch (e) {
console.log('Non existing session with topic:', session.topic)
reject('Non existing session')
}
}
}),

Choose a reason for hiding this comment

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

Are we confident that we don't need this logic anymore?

How do we clear out old sessions that no longer have an active connection?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the problem with that code is that it removes the session if the wallet is not replying. We have the disconnect or disconnectAll methods.

src/lib/dapp/index.ts Outdated Show resolved Hide resolved
Comment on lines +385 to +389
} else {
if (!transaction) throw new Error('Transaction is not defined')
const signer = this.getSigner(paramsOrAccountId as AccountId)
return signer.executeTransaction(transaction)
}

Choose a reason for hiding this comment

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

The else is redundant here (and in the similar transaction functions) since you're already returning if the above condition is true:

Suggested change
} else {
if (!transaction) throw new Error('Transaction is not defined')
const signer = this.getSigner(paramsOrAccountId as AccountId)
return signer.executeTransaction(transaction)
}
}
if (!transaction) throw new Error('Transaction is not defined')
const signer = this.getSigner(paramsOrAccountId as AccountId)
return signer.executeTransaction(transaction)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Completely agree with that @justynspooner but that is opinionable and a matter of preferences in the coding.

paramsOrAccountId: SignAndExecuteTransactionParams | AccountId,
transaction?: Transaction,
) {
if (arguments.length === 1 && isSignAndExecuteTransactionParams(paramsOrAccountId)) {

Choose a reason for hiding this comment

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

Instead of manually creating guards we could probably improve all these checks and the error messages we return by using Zod

This would let us have the static types and the runtime checks bound to the same schema definition.

import { z } from 'zod';

// Create our Zod schema
const SignAndExecuteTransactionParamsSchema = z.object({
  signerAccountId: z.string(),
  transactionList: z.string(),
});

// Infer the TypeScript types that we can use around our app
type SignAndExecuteTransactionParams = z.infer<typeof SignAndExecuteTransactionParamsSchema>;

// Runtime checks
const result = SignAndExecuteTransactionParamsSchema.safeParse(paramsOrAccountId);

if (!result.success) {
  result.error.issues;
  /* [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "number",
        "path": [ "signerAccountId" ],
        "message": "Expected string, received number"
      }
  ] */
}

I don't suggest we need to add this into this PR but I'll leave here if someone (maybe me when I get some cycles) can add a new PR 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.

I don't agree. I think the library should be as raw as possible. However, a new PR to discuss it would be great.

@@ -89,15 +125,32 @@ export class DAppSigner implements Signer {
throw new Error('Method not implemented.')
}

async checkTransaction<T extends Transaction>(transaction: T): Promise<T> {
throw new Error('Method not implemented.')
async signTransaction<T extends Transaction>(transaction: T) {

Choose a reason for hiding this comment

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

Would be good to see some tests for this to improve confidence in its function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, absolutely. I did as much as I could. Let's unlock this contribution and we can continue improving it.

@@ -106,4 +159,47 @@ export class DAppSigner implements Signer {
): Promise<OutputT> {
throw new Error('Method not implemented.')
Copy link
Contributor

Choose a reason for hiding this comment

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

call needs to be implemented to support transaction.executeWithSigner(signer) https://github.com/hashgraph/hedera-sdk-js/blob/65a2323f8d9281d686c2f1c4d337b7a22a3ca97c/src/Executable.js#L500

Copy link
Contributor Author

@franfernandez20 franfernandez20 Apr 17, 2024

Choose a reason for hiding this comment

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

Agree but as for the last comment. Let's unlock this contribution and we can continue improving it.

@@ -106,4 +159,47 @@ export class DAppSigner implements Signer {
): Promise<OutputT> {
throw new Error('Method not implemented.')
}

/*
* Extra methods
Copy link
Contributor

Choose a reason for hiding this comment

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

I see how these methods could be useful to dApp developers. But exposing these methods may back walletconnect into a corner where it's Signer implementation exposes extra methods that dApps become dependent. I think these extra methods should all be private, unless Signer also publicly exposes the same methods.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you want to use separately the signer functionality from the DAppConnector and session manage it is mandatory to keep those methods public. Furthermore, there are no more issues to having them exposed than having nothing to use.

Copy link
Contributor

Choose a reason for hiding this comment

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

If you want to use separately the signer functionality from the DAppConnector and session manage it is mandatory to keep those methods public.

I'm not completely understanding this. Can you explain what you mean by use separately the signer functionality from the DAppConnector and session manage?

My understanding is that DAppConnector should be used to session manage and DAppSigner should be used as an implementation for the hashgraph/sdk's Signer interface. Right now DAppSigner is leaking its abstraction details to the user.

) {}
) {
this.signerAccountId = `${ledgerIdToCAIPChainId(ledgerId)}:${accountId.toString()}`
this.nodesAccountIds = [AccountId.fromString('0.0.3')]
Copy link
Contributor

Choose a reason for hiding this comment

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

this.nodesAccountIds = [AccountId.fromString('0.0.3')] is a single point of failure on consensus node 3. Consider creating a hashgraph/sdk client and grabbing node ids from it's _network property. See this Signer implementation from the hashgraph/sdk https://github.com/hashgraph/hedera-sdk-js/blob/main/src/Wallet.js#L272

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With the sdk client, you don't ensure you use the same nodes as the wallet you are using. So I think the best option is to set manually the available nodes after requesting them to the wallet. Anyway, I think here we have a good point to improve. I suggest creating a new PR to address the issue.

Copy link
Contributor

@pwoosam pwoosam Apr 17, 2024

Choose a reason for hiding this comment

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

With the sdk client, you don't ensure you use the same nodes as the wallet you are using.

I don't think this should be a requirement for walletconnect. The nodeAccountIds on a transaction can differ from the nodes returned by a wallet, as long as they are nodes for the same network.

Copy link

Choose a reason for hiding this comment

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

@franfernandez20 we absolutely cannot be hardcoding node id's like this, if node 3 goes down all transactions using this node will fail

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To set the list, you can use the setNodes function. It is essential to ensure that the nodes in the client and wallet are aligned. Although we need to restore the Transaction.toBytes() approach to retain more nodes, we now have to use this method with only one node.

Anyway, what do you propose?

* @param {AccountId[]} nodesAccountIds
*/
setNodes(nodesAccountIds: AccountId[]): void {
this.nodesAccountIds = nodesAccountIds
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment. I think nodeAccountIds can be fetched more reliably using a Client or Provider instead of allowing the dApp to explicitly set them like this.

If the dApp wants to explicitly set nodeAccountIds, it should do so on the transaction itself instead of DAppSigner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same suggestion :)

this.nodesAccountIds = [AccountId.fromString('0.0.3')]
}

static initialize(signClient: ISignClient) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it was more elegant to pass the signClient in the constructor like it was before instead of using a static method to set a static prop that's shared between all instances.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is debatable. For now, we have nothing to ease the dApp developers' integration. Let's start with this contribution and then discuss minor details like this.


public getSigner(accountId: AccountId): DAppSigner {
const signer = this.signers.find((signer) => signer.getAccountId().equals(accountId))
if (!signer) throw new Error('Signer is not found for this accountId')
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Returning undefined should be ok if not found. The dApp is likely better off handling an undefined result than try/catching this exception.

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 agree


export class DAppSigner implements Signer {
static signClient: ISignClient
public signerAccountId: string
Copy link
Contributor

Choose a reason for hiding this comment

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

Should props that are not already exposed by the Signer interface be private?

} catch (error) {
throw error
} finally {
this.walletConnectModal.closeModal()
Copy link
Contributor

Choose a reason for hiding this comment

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

Excellent catch with this finally block!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Instead of being sarcastic and nitpicking, please be proactive and offer solutions. Also, it would be great if your comments included code solutions. Like other colleagues do.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm sorry for the misunderstanding :( I was genuine with this comment. When errors were thrown previously, the modal stayed open and was unusable. This was an excellent find.

* const result = await dAppConnector.executeTransaction(AccountId.fromString('0.0.12345), transaction)
* ```
*/
public async executeTransaction(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm questioning if we should remove the execute*/sign* methods from the DAppConnector entirely and instead only use the Signer for executing transactions.

It feels like the DAppConnector should limit its responsibility managing walletconnect sessions and extending walletconnect.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree with that but I keep there to be backwards compatible with the previous versions.

Co-authored-by: Justyn Spooner <justynjj@gmail.com>
Signed-off-by: Fran Fernández <franfernandez20@gmail.com>
@pwoosam
Copy link
Contributor

pwoosam commented Apr 17, 2024

@franfernandez20 I don't support merging this PR in its current state. The PR comments I left were marked resolved without any changes.

To unblock this PR, I think it should be split into at least 3 PRs with minimal changes:

  • DAppSigner enhancement
  • Close walletConnectModal on error
  • DAppConnector execution/sign method improvements

A PR should have a single, unique goal. This way contributors can agree on the minimal set of changes and focus discussion on a single topic to unblock the PR.

@pwoosam
Copy link
Contributor

pwoosam commented Apr 18, 2024

I decided to take a shot at the DAppSigner enhancement in another PR: #116

@hgraphql
Copy link
Contributor

hgraphql commented Apr 23, 2024

It looks like there's a bit of unresolved comments that have been marked resolved. Moving forward can we have both the commenter and the contributor agree when a conversation is resolved?

@hgraphql
Copy link
Contributor

I decided to take a shot at the DAppSigner enhancement in another PR: #116

Should we view this as an alternate approach or a complementary PR?

Are there pieces that overlap?

We may want to consider a "release" branch so that we can begin to come to consensus on a more granular basis.

@pwoosam
Copy link
Contributor

pwoosam commented Apr 23, 2024

@hgraphql #116 is an alternate approach.

#116 implements more of Signer interface, enables transaction.executeWithSigner(signer) usage, adds unit tests for the signer, updates the example dApp have new sections to test the signer methods end to end, addresses the feedback in this PR, and adds support for some queries that return array results instead of individual objects.

@pwoosam
Copy link
Contributor

pwoosam commented Apr 23, 2024

There is significant overlap between the PRs, but #116 focuses on only what is needed to support implementing the Signer interface and testing it. #76 has a few other unrelated enhancements in it.

@franfernandez20
Copy link
Contributor Author

@franfernandez20 I don't support merging this PR in its current state. The PR comments I left were marked resolved without any changes.

To unblock this PR, I think it should be split into at least 3 PRs with minimal changes:

  • DAppSigner enhancement
  • Close walletConnectModal on error
  • DAppConnector execution/sign method improvements

A PR should have a single, unique goal. This way contributors can agree on the minimal set of changes and focus discussion on a single topic to unblock the PR.

Indeed, it would be preferable to have fewer PRs. However, we are starting from an unfinished version of this dAppSigner, which complicates the process of having one PR per topic, especially since there are more than three topics involved. While it might require more effort, I will attempt to manage it. Nonetheless, I believe it might be more efficient to first achieve a functional version and then focus on making improvements.

@hgraphql
Copy link
Contributor

Great convos here!

Closing this PR in favor #116 , #134 , #135

If I'm mistaken on this feel free to re-open!

@hgraphql hgraphql closed this May 20, 2024
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.

None yet

6 participants