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/add gridplus lattice1 #3744

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

Conversation

mrcnk
Copy link

@mrcnk mrcnk commented Jun 4, 2024

Changes

  • Adds a new onboarding entry for GridPlus Lattice1.
  • Adds a new service to communicate with Lattice1.
  • Adds a new signer that signs with Lattice1.
  • Allows to sign with Lattice1 in window.ethereum.
  • Adds an E2E test for the onboarding with Lattice1.

Screenshots

CleanShot 2024-06-04 at 15 21 29
CleanShot 2024-06-04 at 15 22 00
CleanShot 2024-06-04 at 15 22 16
CleanShot 2024-06-04 at 15 23 21
CleanShot 2024-06-04 at 15 24 19
CleanShot 2024-06-04 at 15 25 20

@mrcnk mrcnk requested a review from a team as a code owner June 4, 2024 13:25
Copy link
Contributor

@Shadowfiend Shadowfiend left a comment

Choose a reason for hiding this comment

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

A few early comments, more to come this week.

background/services/gridplus/index.ts Outdated Show resolved Hide resolved
background/services/gridplus/index.ts Outdated Show resolved Hide resolved
background/main.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@Shadowfiend Shadowfiend left a comment

Choose a reason for hiding this comment

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

Did a final pass here, I think a couple of questions remain on my end:

  • Am I reading this right that this does not accommodate multiple Lattice1s? The data model in the service does not seem to allow for that, but I could be misreading. It needs to be massaged anyway (from local storage to IndexedDB), so it's not a massive deal.
  • Does the current setup assume that the Lattice1 will always be connected? In particular, it seems like it doesn't handle connection status, and my understanding is the Lattice1 is communicated with over the internet—so a failure in the wifi router, having the Lattice1 off, etc, could result in the Lattice1 not actually being available.

There are two notes below that requires some work I'll be doing (one is about renaming from gridplus to grid-plus or gridPlus and Gridplus to GridPlus, depending on where we are, just for consistency; the other is about a specific aspect of internal-ethereum-provider). The rest I don't think is a blocker, and we can merge & release with future iterations of work addressing the remainder.

}

const gridplusSlice = createSlice({
name: "gridplus",
Copy link
Contributor

Choose a reason for hiding this comment

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

Everywhere we refer to gridplus should be grid-plus, including filenames, as capital letters/separate words translate to - when we move outside of camel-casing. Anywhere we then import gridplus should be gridPlus, etc, for consistency.

@@ -503,10 +503,13 @@ export default class InternalEthereumProviderService extends BaseService<Events>
// Ethers does not want to see the EIP712Domain field, extract it.
const { EIP712Domain, ...typesForSigning } = params.typedData.types

const { domain } = params.typedData
domain.chainId = params.account.network.chainID
Copy link
Contributor

Choose a reason for hiding this comment

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

Reading EIP-712, what should be happening here is that the signer should fail a signature with a mismatch between the network id and the current active network. In practice that means we should drop this step and instead fail at the callsite, line 179, or move the callsite's account resolution into here and fail in here in that case, only passing the JSON param from there.

I can make this change.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hm, reading further, we actually catch this in the SigningService right now, where we check the account network chainID against the chainID passed in the data:

let signedData: string
const chainId =
typeof typedData.domain.chainId === "string"
? // eslint-disable-next-line radix
parseInt(typedData.domain.chainId)
: typedData.domain.chainId
if (
typedData.domain.chainId !== undefined &&
// Let parseInt infer radix by prefix; chainID can be hex or decimal,
// though it should generally be hex.
// eslint-disable-next-line radix
chainId !== parseInt(account.network.chainID)
) {
throw new Error(
"Attempting to sign typed data with mismatched chain IDs.",
)
}

With this in mind, the above code actually circumvents this explicit error in favor of an implicit one (an invalid signature), so I'll be removing this code and leaving the current paths unchanged; please let me know if there was a strong reason for this adjustment, though.

@@ -21,6 +21,8 @@ const getSignerRecordId = (signer: AccountSignerWithId): SignerRecordId => {
return `${signer.type}/${signer.keyringID}`
case "private-key":
return `${signer.type}/${signer.walletID}`
case "gridplus":
return `${signer.type}/${signer.path}`
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that the purpose here is to distinguish “signers” in the sense of accounts that would be derived from the same place. For Ledger, each “device” would get its own signer. I'm worried that path here refers to a full derivation path, which would mean each address would get its own “signer”. That results in poor UX because each signer gets a grouping in the selector list, with accounts getting entries under the signer; for example, here, “Taho 1” is a signer, and testertesting.eth is an account:

Screenshot 2024-10-05 at 20 59 47

If every account added from a GridPlus device corresponds to a grouping, that'll potentially create a lot of clutter—instead.

Ultimately right now this is only used to manage that category title (“Taho 1” in the example above), so it's not a huge blocker—but it may be worth considering if it would be better to funnel the device id out of the service so that it can be used for differentiation here like it is for Ledgers.


async readClient() {
this.client =
(await storage.local.get(CLIENT_STORAGE_KEY))?.[CLIENT_STORAGE_KEY] ??
Copy link
Contributor

Choose a reason for hiding this comment

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

Services generally don't read or write from local storage—that's considered only for ephemeral data. Services instead own an IndexedDB database (see e.g. ledger/db.ts) that handles querying, etc.

I don't think this is a blocker, but it's a relatively important part of the underlying architecture as local storage has aggressive size quotas and can fail in unexpected or silent ways when those size quotas are exceeded, while IndexedDB has a much more generous quote and clearer failure modes.

Comment on lines +72 to +80
async readAddresses() {
const persistedAddresses =
(await storage.local.get(ADDRESSES_STORAGE_KEY))?.[
ADDRESSES_STORAGE_KEY
] ?? "[]"
const activeAddresses = JSON.parse(persistedAddresses)
this.activeAddresses = activeAddresses
return this.activeAddresses
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Currently this happens in Popup.tsx via a hook, but instead it should happen during the internalStartService call that triggers when the service starts up (as the service's lifecycle is typically longer than the lifecycle of the popup).

}

async pairDevice({ pairingCode }: { pairingCode: string }) {
await this.readClient()
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the purpose of running this every time pairDevice, fetchAddresses, etc are run? It seems like it just reads data that's been serialized, but that data remains in-memory, no? I don't see it cleared anywhere.

Also, is this.client secret/sensitive data or is it irrelevant if someone else gets access to it?

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.

2 participants