-
Notifications
You must be signed in to change notification settings - Fork 15
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
Add walletconnect provider #114
Changes from all commits
d09c062
09d1870
db410a7
003b98c
4e850f2
bd78c18
2262352
152fc4a
aec2484
feed1c6
d0431a2
a26ce70
c33b8ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
import { send as webhook } from './webhook'; | ||
import { send as walletconnect } from './walletconnectNotify'; | ||
import { send as discord } from './discord'; | ||
import { send as beams } from './beams'; | ||
import { send as xmtp } from './xmtp'; | ||
|
||
export default [ | ||
// Comment a line to disable a provider | ||
webhook, | ||
discord, | ||
beams, | ||
xmtp | ||
// webhook | ||
// discord, | ||
// beams, | ||
// xmtp | ||
walletconnect | ||
]; |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,161 @@ | ||||||
import fetch from 'node-fetch'; | ||||||
import { capture } from '@snapshot-labs/snapshot-sentry'; | ||||||
|
||||||
const WALLETCONNECT_NOTIFY_SERVER_URL = process.env.WALLETCONNECT_NOTIFY_SERVER_URL; | ||||||
const WALLETCONNECT_PROJECT_SECRET = process.env.WALLETCONNECT_PROJECT_SECRET; | ||||||
const WALLETCONNECT_PROJECT_ID = process.env.WALLETCONNECT_PROJECT_ID; | ||||||
|
||||||
const AUTH_HEADER = { | ||||||
Authorization: WALLETCONNECT_PROJECT_SECRET ? `Bearer ${WALLETCONNECT_PROJECT_SECRET}` : '' | ||||||
}; | ||||||
|
||||||
// Rate limiting numbers: | ||||||
const MAX_ACCOUNTS_PER_REQUEST = 500; | ||||||
const PER_SECOND_RATE_LIMIT = 2; | ||||||
const WAIT_ERROR_MARGIN = 0.25; | ||||||
|
||||||
// Rate limiting logic: | ||||||
async function wait(seconds: number) { | ||||||
return new Promise<void>(resolve => { | ||||||
setTimeout(resolve, seconds * 1_000); | ||||||
}); | ||||||
} | ||||||
|
||||||
// Match Snapshot event names to notification types | ||||||
// That should be defined in the wc-notify-config.json | ||||||
function getNotificationType(event) { | ||||||
if (event.includes('proposal/')) { | ||||||
return 'ed2fd071-65e1-440d-95c5-7d58884eae43'; | ||||||
} else { | ||||||
return null; | ||||||
} | ||||||
} | ||||||
|
||||||
// Generate a notification body per the event | ||||||
function getNotificationBody(event, space) { | ||||||
switch (event) { | ||||||
case 'proposal/create': | ||||||
ChaituVR marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
return `A new proposal has been created for ${space.name}`; | ||||||
case 'proposal/end': | ||||||
return `A proposal has closed for ${space.name}`; | ||||||
Comment on lines
+39
to
+40
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I talked to team about this, better we should be jusing just 'proposal/start' instead of created/end |
||||||
default: | ||||||
return null; | ||||||
} | ||||||
} | ||||||
|
||||||
// Fetch subscribers from WalletConnect Notify server | ||||||
export async function getSubscribersFromWalletConnect() { | ||||||
const fetchSubscribersUrl = `${WALLETCONNECT_NOTIFY_SERVER_URL}/${WALLETCONNECT_PROJECT_ID}/subscribers`; | ||||||
|
||||||
try { | ||||||
const subscribersRs = await fetch(fetchSubscribersUrl, { | ||||||
headers: AUTH_HEADER | ||||||
}); | ||||||
|
||||||
const subscribers: string[] = await subscribersRs.json(); | ||||||
|
||||||
return subscribers; | ||||||
} catch (e) { | ||||||
capture('[WalletConnect] failed to fetch subscribers'); | ||||||
return []; | ||||||
} | ||||||
Comment on lines
+58
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we catching and suppressing this error? If we can't get subscribers then the functions that call this should fail as well, not proceed with an empty list. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the other providers like discord and xmtp don't throw and just end up being a noop so I did the same here |
||||||
} | ||||||
|
||||||
// Find the CAIP10 of subscribers, since the Notify API requires CAIP10. | ||||||
async function crossReferenceSubscribers(space: { id: string }, spaceSubscribers) { | ||||||
const subscribersFromDb = spaceSubscribers; | ||||||
const subscribersFromWalletConnect = await getSubscribersFromWalletConnect(); | ||||||
|
||||||
// optimistically reserve all subscribers from the db | ||||||
const crossReferencedSubscribers = new Array(subscribersFromDb.length); | ||||||
|
||||||
// Create a hashmap for faster lookup | ||||||
const addressPrefixMap = new Map<string, string>(); | ||||||
for (const subscriber of subscribersFromWalletConnect) { | ||||||
const unprefixedAddress = subscriber.split(':').pop(); | ||||||
if (unprefixedAddress) { | ||||||
addressPrefixMap.set(unprefixedAddress, subscriber); | ||||||
} | ||||||
} | ||||||
|
||||||
for (const subscriber of subscribersFromDb) { | ||||||
const crossReferencedAddress = addressPrefixMap.get(subscriber); | ||||||
if (crossReferencedAddress) { | ||||||
crossReferencedSubscribers.push(crossReferencedAddress); | ||||||
} | ||||||
} | ||||||
|
||||||
// remove empty elements from the array, since some might not have been found in WalletConnect Notify server | ||||||
return crossReferencedSubscribers.filter(addresses => addresses); | ||||||
} | ||||||
|
||||||
async function queueNotificationsToSend(notification, accounts: string[]) { | ||||||
for (let i = 0; i < accounts.length; i += MAX_ACCOUNTS_PER_REQUEST) { | ||||||
await sendNotification(notification, accounts.slice(i, i + MAX_ACCOUNTS_PER_REQUEST)); | ||||||
const waitTime = 1 / PER_SECOND_RATE_LIMIT + WAIT_ERROR_MARGIN; | ||||||
await wait(waitTime); | ||||||
} | ||||||
} | ||||||
|
||||||
export async function sendNotification(notification, accounts) { | ||||||
const notifyUrl = `${WALLETCONNECT_NOTIFY_SERVER_URL}/${WALLETCONNECT_PROJECT_ID}/notify`; | ||||||
|
||||||
const body = { | ||||||
accounts, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Also has a rate limit of 2 per second so you may need to add some artificial timing e.g. with date math and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll refactor to fix this |
||||||
notification | ||||||
}; | ||||||
|
||||||
try { | ||||||
const notifyRs = await fetch(notifyUrl, { | ||||||
method: 'POST', | ||||||
headers: { | ||||||
...AUTH_HEADER, | ||||||
'Content-Type': 'application/json' | ||||||
}, | ||||||
body: JSON.stringify(body) | ||||||
}); | ||||||
|
||||||
const notifySuccess = await notifyRs.json(); | ||||||
|
||||||
return notifySuccess; | ||||||
} catch (e) { | ||||||
capture('[WalletConnect] failed to notify subscribers', e); | ||||||
} | ||||||
ChaituVR marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
|
||||||
// Transform proposal event into notification format. | ||||||
async function formatMessage(event, proposal) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
const space = proposal.space; | ||||||
if (!space) return null; | ||||||
|
||||||
const notificationType = getNotificationType(event.event); | ||||||
const notificationBody = getNotificationBody(event.event, space); | ||||||
|
||||||
if (!notificationType) { | ||||||
capture(`[WalletConnect] could not get matching notification type for event ${event.event}`); | ||||||
return; | ||||||
} | ||||||
|
||||||
if (!notificationBody) { | ||||||
capture(`[WalletConnect] could not get matching notification body for event ${event.event}`); | ||||||
return; | ||||||
} | ||||||
|
||||||
const url = new URL(proposal.link); | ||||||
url.searchParams.append('app', 'walletconnect'); | ||||||
Comment on lines
+144
to
+145
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is resulting in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this is fine There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the purpose of Reason for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It is used by snapshot to know that someone voted from web3inbox, changed it to |
||||||
|
||||||
return { | ||||||
title: proposal.title, | ||||||
body: notificationBody, | ||||||
url: url.toString(), | ||||||
icon: space.avatar, | ||||||
type: notificationType | ||||||
}; | ||||||
} | ||||||
|
||||||
export async function send(event, proposal, subscribers) { | ||||||
const crossReferencedSubscribers = await crossReferenceSubscribers(proposal.space, subscribers); | ||||||
const notificationMessage = formatMessage(event, proposal); | ||||||
|
||||||
await queueNotificationsToSend(notificationMessage, crossReferencedSubscribers); | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -753,6 +753,14 @@ | |
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" | ||
integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== | ||
|
||
"@types/node-fetch@^2.6.6": | ||
version "2.6.6" | ||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.6.tgz#b72f3f4bc0c0afee1c0bc9cff68e041d01e3e779" | ||
integrity sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw== | ||
dependencies: | ||
"@types/node" "*" | ||
form-data "^4.0.0" | ||
|
||
"@types/node@*": | ||
version "20.4.7" | ||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.7.tgz#74d323a93f1391a63477b27b9aec56669c98b2ab" | ||
|
@@ -1095,6 +1103,11 @@ async-mutex@^0.4.0: | |
dependencies: | ||
tslib "^2.4.0" | ||
|
||
asynckit@^0.4.0: | ||
version "0.4.0" | ||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" | ||
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== | ||
|
||
available-typed-arrays@^1.0.5: | ||
version "1.0.5" | ||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" | ||
|
@@ -1261,6 +1274,13 @@ color-name@~1.1.4: | |
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" | ||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== | ||
|
||
combined-stream@^1.0.8: | ||
version "1.0.8" | ||
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" | ||
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== | ||
dependencies: | ||
delayed-stream "~1.0.0" | ||
|
||
[email protected]: | ||
version "0.0.1" | ||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" | ||
|
@@ -1376,6 +1396,11 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: | |
has-property-descriptors "^1.0.0" | ||
object-keys "^1.1.1" | ||
|
||
delayed-stream@~1.0.0: | ||
version "1.0.0" | ||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" | ||
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== | ||
|
||
[email protected]: | ||
version "2.0.0" | ||
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" | ||
|
@@ -1897,6 +1922,15 @@ for-each@^0.3.3: | |
dependencies: | ||
is-callable "^1.1.3" | ||
|
||
form-data@^4.0.0: | ||
version "4.0.0" | ||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" | ||
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== | ||
dependencies: | ||
asynckit "^0.4.0" | ||
combined-stream "^1.0.8" | ||
mime-types "^2.1.12" | ||
|
||
[email protected]: | ||
version "0.2.0" | ||
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" | ||
|
@@ -2524,7 +2558,7 @@ [email protected]: | |
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" | ||
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== | ||
|
||
mime-types@~2.1.24, mime-types@~2.1.34: | ||
mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: | ||
version "2.1.35" | ||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" | ||
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== | ||
|
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.
I think this one is for new app
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 - please feel free to adjust the notification types per your cloud configuration