-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add walletconnect notify provider for web3Inbox (#162)
* Add walletconnect provider * chore: fix bug causing an invalid array to be passed as subscribers * Remove unused console log * chore: Add documentation * chore: add env vars to example file * Add notification body gen * chore: remove switch statement * fix: use new notification id * feat: cross reference subscribers * chore: account for rate limiting * Apply suggestions from code review Co-authored-by: Less <[email protected]> * feat: use subscribers coming from the function, less query * chore: run lint fix * New fixes * lint fix * rename provider name * Fixes * Remove empty line changes to reduce changes * Move out wait time variable --------- Co-authored-by: Celine Sarafa <[email protected]> Co-authored-by: Less <[email protected]>
- Loading branch information
1 parent
74f3c24
commit d8997fc
Showing
5 changed files
with
191 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
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 WALLETCONNECT_NOTIFICATION_TYPE = | ||
process.env.WALLETCONNECT_NOTIFICATION_TYPE; | ||
|
||
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; | ||
const WAIT_TIME = 1 / PER_SECOND_RATE_LIMIT + WAIT_ERROR_MARGIN; | ||
|
||
// Rate limiting logic: | ||
async function wait(seconds: number) { | ||
return new Promise<void>(resolve => { | ||
setTimeout(resolve, seconds * 1_000); | ||
}); | ||
} | ||
|
||
// 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 []; | ||
} | ||
} | ||
|
||
// 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) | ||
); | ||
|
||
await wait(WAIT_TIME); | ||
} | ||
} | ||
|
||
export async function sendNotification(notification, accounts) { | ||
const notifyUrl = `${WALLETCONNECT_NOTIFY_SERVER_URL}/${WALLETCONNECT_PROJECT_ID}/notify`; | ||
|
||
const body = { | ||
accounts, | ||
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); | ||
} | ||
} | ||
|
||
// Transform proposal event into notification format. | ||
function formatMessage(event, proposal) { | ||
const space = proposal.space; | ||
if (!space) return null; | ||
|
||
const notificationType = WALLETCONNECT_NOTIFICATION_TYPE; | ||
const notificationBody = `🟢 New proposal on ${space.name} @${space.id}\n\n`; | ||
|
||
const url = `${proposal.link}?app=web3inbox`; | ||
return { | ||
title: proposal.title, | ||
body: notificationBody, | ||
url, | ||
icon: space.avatar, | ||
type: notificationType | ||
}; | ||
} | ||
|
||
export async function send(event, proposal, subscribers) { | ||
if (event.event !== 'proposal/start') return; | ||
const crossReferencedSubscribers = await crossReferenceSubscribers( | ||
proposal.space, | ||
subscribers | ||
); | ||
const notificationMessage = formatMessage(event, proposal); | ||
|
||
await queueNotificationsToSend( | ||
notificationMessage, | ||
crossReferencedSubscribers | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1385,6 +1385,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" | ||
|
@@ -1779,6 +1787,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" | ||
|
@@ -2123,6 +2136,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" | ||
|
@@ -2284,6 +2304,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" | ||
|
@@ -2917,6 +2942,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" | ||
|
@@ -4171,7 +4205,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== | ||
|