-
Notifications
You must be signed in to change notification settings - Fork 105
feat: Add Balance Subscriptions feature #849
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
Conversation
|
We're building your pull request over on Zeet. |
| client: thirdwebClient, | ||
| }); | ||
|
|
||
| currentBalance = await balanceOf({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait what is contract here? this is to get a erc20 balance, that's not what you want here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you can subscribe to either erc20 balance or native balance changes
- Add support for creating new webhooks with optional labels - Allow using existing webhooks by ID - Validate webhook event type and revocation status - Improve error handling for webhook creation and selection
- Replace manual balance fetching with `getWalletBalance` method - Support both native token and ERC20 token balance retrieval - Remove redundant contract and RPC client initialization code
…ions - Update Prisma schema, migration, and database indexes - Modify TypeScript interfaces and schemas across services and routes - Ensure consistent naming for token-related address fields - Update worker and database interaction methods
| description: "Webhook URL to create a new webhook", | ||
| examples: ["https://example.com/webhook"], | ||
| }), | ||
| webhookLabel: Type.Optional( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can make this non-optional
| threshold: Type.Optional( | ||
| Type.Object({ | ||
| min: Type.Optional(Type.String({ | ||
| description: "Minimum balance threshold", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want the dev to provide wei, we should explicitly make that clear and remind them the token's decimals matter. E.g. 0.1 ETH and 0.1 USDC will have different values.
I'm debating if tokens ("0.1") is more intuitive and less error prone (to get decimals wrong). Also precision doesn't matter because it's just a threshold anyway.
src/prisma/schema.prisma
Outdated
| cursorDelaySeconds Int @default(2) @map("cursorDelaySeconds") | ||
| contractSubscriptionsRetryDelaySeconds String @default("10") @map("contractSubscriptionsRetryDelaySeconds") | ||
| balanceSubscriptionsCronSchedule String? @map("balanceSubscriptionsCronSchedule") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expose this in some config update route?
| /** | ||
| * Array of conditions to monitor for this wallet | ||
| */ | ||
| conditions: Array<({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make a reusable type for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's autogen 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh lol mb
| validateConditions, | ||
| } from "../../shared/schemas/wallet-subscription-conditions"; | ||
|
|
||
| interface WalletSubscriptionWithWebhook { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't need to define a different interface right? Shouldn't we be passing the type
import type { WalletSubscription } from "@prisma/client" around?
Or if we intend to pass the type joined with Webhook, then something like:
type WalletSubscriptionWithWebhook =
WalletSubscriptions & {
webhook: Webhooks
}| import type { WalletCondition } from "../../shared/schemas/wallet-subscription-conditions"; | ||
| import type { Webhooks } from "@prisma/client"; | ||
|
|
||
| interface WalletSubscriptionWithWebhook { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment, we shouldn't need to define a new interface
| chain, | ||
| }); | ||
|
|
||
| if (currentValue && subscription.webhookId && subscription.webhook) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't webhook always be set already?
We should assert(subscription.webhook) above then and not do any work if there's no valid webhook to even receive the request (e.g. deleted webhook).
| } | ||
| } catch (error) { | ||
| // Log error but continue processing other subscriptions | ||
| const message = error instanceof Error ? error.message : String(error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use prettifyError(error)
| const subscriptions = await getAllWalletSubscriptions({ | ||
| page: 1, | ||
| limit: 1000, // Process 1000 subscriptions at a time | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't put this limit here because Engine allows creating > 1k subscriptions, and any more are silently ignored.
Instead just enforce this limit on the "create wallet subscription" endpoint: If > 1k non-deleted subscriptions, don't allow creating any more.
| default: { | ||
| // For TypeScript exhaustiveness check | ||
| const _exhaustiveCheck: never = condition; | ||
| return undefined; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need this if you type condition.type properly. Typescript knows theres no unhandled cases.
I see WalletCondition already should have type: 'token_balance_lt' | 'token_balance_gt' because it infers from a Zod schema that enforces this. Does typescript error if you remove this default case?
| condition: WalletCondition; | ||
| walletAddress: string; | ||
| chain: Chain; | ||
| }): Promise<string | undefined> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: null seems more correct than undefined here, as an explicit "I checked and there is no value to return".
PR-Codex overview
This PR introduces a
WalletSubscriptionsfeature, allowing users to create, update, retrieve, and delete wallet subscriptions based on specified conditions. It also integrates a worker to process these subscriptions and trigger webhooks when conditions are met.Detailed summary
WalletSubscriptionsServicefor managing wallet subscriptions.add,update,get-all, anddelete.WalletSubscriptionWorker) to process subscriptions and trigger webhooks.wallet_subscriptions.WALLET_SUBSCRIPTION.