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(jumpstart): handle jumpstart state #4963

Merged
merged 34 commits into from
Mar 1, 2024
Merged

Conversation

bakoushin
Copy link
Contributor

@bakoushin bakoushin commented Feb 23, 2024

Description

  1. Introducing new slice to the redux state for jumpstart link handling:
jumpstart: {
  showLoading: boolean
  showError: boolean
}

This state is expected to be used for rendering UI components displaying the jumpstart link handling state to the user. The actual components will follow in the subsequent PR once the design discussions converge.

  1. After successful jumpstart claim two types of pending transactions are dispatched (getting tx data trough blockchain RPC calls):
  • Payment received (for ERC-20 claim)
  • NFT received (for ERC-721 claim)

Those transactions are expected to be replaced with blockchain-api data once it's fetched.

  1. Added analytics events for jumpstart handler lifecycle:
  • claim succeeded
  • claim failed
  • ERC20 token claimed
  • ERC721 nft claimed

Test plan

  • Updated unit tests

Related issues

Backwards compatibility

Y

Network scalability

If a new NetworkId and/or Network are added in the future, the changes in this PR will:

  • Continue to work without code changes, OR trigger a compilation error (guaranteeing we find it when a new network is added)

Copy link

codecov bot commented Feb 23, 2024

Codecov Report

Attention: Patch coverage is 98.50746% with 2 lines in your changes are missing coverage. Please review.

Project coverage is 85.48%. Comparing base (b2b7890) to head (86e4e85).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4963      +/-   ##
==========================================
+ Coverage   85.43%   85.48%   +0.05%     
==========================================
  Files         721      724       +3     
  Lines       29435    29534      +99     
  Branches     5093     5100       +7     
==========================================
+ Hits        25148    25248     +100     
+ Misses       4048     4047       -1     
  Partials      239      239              
Files Coverage Δ
src/analytics/Events.tsx 100.00% <100.00%> (ø)
src/analytics/Properties.tsx 100.00% <ø> (ø)
src/app/saga.ts 73.16% <100.00%> (+0.09%) ⬆️
src/jumpstart/saga.ts 100.00% <100.00%> (ø)
src/jumpstart/selectors.ts 100.00% <100.00%> (ø)
src/jumpstart/slice.ts 100.00% <100.00%> (ø)
src/nfts/types.ts 100.00% <ø> (ø)
src/redux/migrations.ts 97.27% <100.00%> (+<0.01%) ⬆️
src/redux/reducers.ts 94.59% <100.00%> (+0.15%) ⬆️
src/redux/store.ts 79.36% <ø> (ø)
... and 4 more

... and 2 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b2b7890...86e4e85. Read the comment docs.

Copy link
Collaborator

@kathaypacific kathaypacific left a comment

Choose a reason for hiding this comment

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

looking great!! perhaps you want to add the jumpstart state to the redux blacklist or restore the initial state via the rehydrate action, but it seems like we wouldn't want to persist this store across app sessions?

yield* put(jumpstartClaimStarted())

const transactionHashes = yield* call(jumpstartLinkHandler, privateKey, walletAddress)
yield* fork(dispatchPendingTransactions, transactionHashes)
Copy link
Collaborator

Choose a reason for hiding this comment

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

i think we don't do a great job of handling multiple chains here. on one hand, we've made the dynamic config work for multichain by enabling a different contract address for each chain but in the code we're only looking for the defaultNetworkId. in both the jumpstartLinkHandler and 'dispatchPendingTransactions' sagas only look at the default network.

could we update this to be something like get all the supported networks based on what is there in the dynamic config -> for each chain, fork a saga that calls jumpstartLinkHandler with the chain id and then does the dispatchPendingTransactions for those transactions?

Copy link
Contributor Author

@bakoushin bakoushin Feb 27, 2024

Choose a reason for hiding this comment

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

I agree that we should handle networks in a generic way and avoid network hardcoding. Thanks for pointing this out!

I ended up looping through all remote config networkIds, but I think it's suboptimal. If the amount of jumpstart-supported networks grows, we would have to loop trough them all, issuing network/blockchain request for every network because we have no idea for which specific network that link is. To avoid looping, perhaps we can encode network id within the jumpstart link?

Copy link
Contributor Author

@bakoushin bakoushin Feb 27, 2024

Choose a reason for hiding this comment

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

Also, we probably want to refactor jumpstartLinkHandler from ContractKit to Viem to make it multichain?

For now, I just made it throw an error if network id isn't Celo.

const transactionHashes = [
...(await executeClaims(kit, jumpstart, publicKey, userAddress, 'erc20', privateKey)),
...(await executeClaims(kit, jumpstart, publicKey, userAddress, 'erc721', privateKey)),
]
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: could we do a Promise.all here?

interface WalletJumpstartProperties {
[JumpstartEvents.jumpstart_started]: undefined
[JumpstartEvents.jumpstart_succeeded]: undefined
[JumpstartEvents.jumpstart_failed]: undefined
Copy link
Collaborator

Choose a reason for hiding this comment

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

i wonder if we need the jumpstart_started event, when it will always end with jumpstart_succeeded or jumpstart_failed? maybe i'm missing the extra information we get from this event.

i also wonder if it'd be nice to capture some data on how users are using this feature, e.g. the token and amounts that are being claimed. i guess we can also see this on the contract events directly, but maybe it helps with data analysis to capture this in analytics? i'm not sure, just a suggestion :)

Copy link
Contributor Author

@bakoushin bakoushin Feb 27, 2024

Choose a reason for hiding this comment

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

yes, jumpstart_started is redundant.

added separate events for token/amount tracking:

  • jumpstart_claimed_token with ERC20 claim details
  • jumpstart_claimed_nft with ERC721 claim details

I think it's worth keeping a bare jumpstart_succeeded event as well, because getting claim details may fail (but it doesn't mean claim didn't succeeded)

Copy link

emerge-tools bot commented Feb 26, 2024

1 build increased size

Name Version Download Change Install Change Approval
Celo (test)
org.celo.mobile.test
1.79.0 (144) 24.2 MB ⬆️ 4.3 kB (0.02%) 60.3 MB ⬆️ 12.3 kB (0.02%) N/A

Celo (test) 1.79.0 (144)
org.celo.mobile.test

⚖️ Compare build
📦 Install build
⏱️ Analyze build performance

Total install size change: ⬆️ 12.3 kB (0.02%)
Total download size change: ⬆️ 4.3 kB (0.02%)

Largest size changes

Item Install Size Change
main.jsbundle ⬆️ 12.3 kB

🛸 Powered by Emerge Tools

@bakoushin
Copy link
Contributor Author

perhaps you want to add the jumpstart state to the redux blacklist or restore the initial state via the rehydrate action, but it seems like we wouldn't want to persist this store across app sessions?

yes, this state shouldn't persist, because it's only for presentation purposes during current user session. added it to persist's blacklist. thanks for pointing this out!

Copy link
Collaborator

@kathaypacific kathaypacific left a comment

Choose a reason for hiding this comment

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

looking great!! sorry i had a different thought while reviewing how we make the claim here, lmk what you think

userAddress: string
): Promise<Hash[]> {
if (networkId !== networkConfig.defaultNetworkId) {
throw new Error(`Unsupported network id: ${networkId}`)
Copy link
Collaborator

Choose a reason for hiding this comment

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

ah, i was wondering why we need to do this, and then i saw that this function is using contract kit 😞 let's leave it for now and make a task to remove contract kit for later

Copy link
Contributor Author

Choose a reason for hiding this comment

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


yield* fork(dispatchPendingTransactions, networkId, transactionHashes)
} catch (error) {
Logger.error(TAG, `Error handling jumpstart link for ${networkId}`, error)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this claim saga is kicked off every time a jumpstart deep link is consumed, but presumably each deep link is only for one network. if we had multichain jumpstart, this logger.error would always be fired because some chains will always have no transactions found right? and we'd always be doing unnecessary work, to try to claim across chains when we should only do it for one chain? perhaps what would be more efficient is to have the jumpstart link contain the network id...what do you think? sorry, i know this is different to what i suggested last time

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, I agree that we have to store the chain id somewhere in the jumpstart link to avoid unnecessary looping over all supported networks.
should we have a separate task for that?

in the meanwhile we can assume that we get network id from the link, right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yes let's do that! since we create the link when the user makes the transaction, we'll already have this information and no reason why we can't add it to the link to make our lives easier

Copy link
Collaborator

Choose a reason for hiding this comment

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

let's make the network id pathParts[3] of the jumpstart link

Comment on lines 4 to 5
showLoading: boolean
showError: boolean
Copy link
Member

Choose a reason for hiding this comment

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

Just checking, do we need both loading and error state at the same time?
If not, I'd advise to model it differently to avoid impossible states.

For instance:

type Status = 'idle' | 'loading' | 'success' | 'error'

Copy link
Contributor Author

@bakoushin bakoushin Feb 28, 2024

Choose a reason for hiding this comment

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

good point, thanks! refactored it to have just one consistent property:

export interface State {
  claimStatus: 'idle' | 'loading' | 'error'
}

Copy link
Collaborator

@kathaypacific kathaypacific left a comment

Choose a reason for hiding this comment

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

looks great!! a few non blocking annoying comments, do with them what you want :)

@@ -43,27 +39,23 @@ describe('jumpstartLinkHandler', () => {
it('calls executeClaims with correct parameters', async () => {
;(fetchWithTimeout as jest.Mock).mockImplementation(() => ({
ok: true,
json: async () => ({ transactionHash: '0xTEST' }),
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: maybe this can be different from the contractAddress?

src/app/saga.ts Outdated
@@ -298,12 +299,6 @@ export function* handleDeepLink(action: OpenDeepLink) {
const { isSecureOrigin } = action
Logger.debug(TAG, 'Handling deep link', deepLink)

const walletAddress = yield* select(walletAddressSelector)
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: i wonder if we should leave this here and continue to expect the wallet address as a param in the jumpstartClaim saga. only the jumpstartClaim saga is technically needing the wallet address right now, so i understand why you made the change.

it's been annoying in the past for deep links to be processed when wallet address is not available. for example, the opened_via_invite_url event ended up being unhelpful because the walletAddress was undefined and we couldn't match inviters and invitees. the logic in the wallet has changed now where this saga shouldn't be triggered unless the walletAddress is available, but that is done in a hook and perhaps nice to fail early here if something goes wrong?

Copy link
Contributor Author

@bakoushin bakoushin Feb 28, 2024

Choose a reason for hiding this comment

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

but failing early will mean we don't show the user any feedback about the error, right?

we could add a generic alert, but failing inside the specific handler means we could communicate what specific situation we are processing, and what error we've encountered

Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm yes, i guess i can see it both ways.

failing early here = we assert all deep links should be handled after the wallet is initialized and it does not matter whether the deep link strictly needs the wallet address in the logic. even if the logic succeeds, our instrumentation will be broken becuase the wallet address is missing.

failing contextually = we have more information about where it failed, but we will allow silent instrumentation failures.

i have a preference for the first scenario, because i cannot think of a scenario where we'd want to process a deep link when a wallet is not initialised?

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. There may be a logic requiring a wallet address not directly in a deep link handler, down the user journey, like when link handler fires a navigate event.


const token = tokensById[tokenId]
if (!token) {
Logger.error(TAG, 'Claimed unknown tokenId', tokenId)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Logger.error(TAG, 'Claimed unknown tokenId', tokenId)
Logger.warn(TAG, 'Claimed unknown tokenId', tokenId)

seems that we wouldn't want to investigate this right?

tokenId: tokenId.toString(),
})
} catch (error) {
Logger.error(TAG, 'Error adding pending NFT transaction', error)
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here, i wonder if we should downgrade these to .warn since the pending transactions is a nice to have visual feedback rather than core functionality?

@bakoushin bakoushin enabled auto-merge March 1, 2024 10:52
@bakoushin bakoushin added this pull request to the merge queue Mar 1, 2024
Merged via the queue into main with commit 7672934 Mar 1, 2024
16 checks passed
@bakoushin bakoushin deleted the alex/jumpstart-handler-state branch March 1, 2024 11:48
shottah pushed a commit to zed-io/kolektivo that referenced this pull request May 15, 2024
### Description

1. Introducing new slice to the redux state for jumpstart link handling:
```
jumpstart: {
  showLoading: boolean
  showError: boolean
}
```
This state is expected to be used for rendering UI components displaying
the jumpstart link handling state to the user. The actual components
will follow in the subsequent PR once the design discussions converge.

2. After successful jumpstart claim two types of pending transactions
are dispatched (getting tx data trough blockchain RPC calls):
* Payment received (for ERC-20 claim)
* NFT received (for ERC-721 claim)

Those transactions are expected to be replaced with blockchain-api data
once it's fetched.

3. Added analytics events for jumpstart handler lifecycle:
* claim succeeded
* claim failed
* ERC20 token claimed
* ERC721 nft claimed

### Test plan

* Updated unit tests

### Related issues

- Related to RET-1004

### Backwards compatibility

Y

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [x] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
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