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

Add walletconnect provider #114

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@snapshot-labs/prettier-config": "^0.1.0-beta.7",
"@types/express": "^4.17.11",
"@types/node": "^18.0.0",
"@types/node-fetch": "^2.6.6",
"eslint": "^8.47.0",
"prettier": "^2.8.0"
},
Expand Down
4 changes: 2 additions & 2 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import express from 'express';
import { sendEvent } from './providers/webhook';
import { send } from './providers/walletconnectNotify';
import { capture } from '@snapshot-labs/snapshot-sentry';

const router = express.Router();
Expand All @@ -16,7 +16,7 @@ router.get('/test', async (req, res) => {

try {
new URL(url);
await sendEvent(event, url, method);
await send(event, url, method);

return res.json({ url, success: true });
} catch (e: any) {
Expand Down
14 changes: 14 additions & 0 deletions src/helpers/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ config.acquireTimeout = 60e3;
config.timeout = 60e3;
config.charset = 'utf8mb4';
bluebird.promisifyAll([Pool, Connection]);

console.log({
config: {
connectionLimit: config.connectionLimit,
multipleStatements: config.multipleStatements,
database: config.database,
host: config.host,
port: config.port,
connectTimeout: config.connectTimeout,
acquireTimeout: config.acquireTimeout,
timeout: config.timeout,
charset: config.charset
}
});
const db = mysql.createPool(config);

export default db;
10 changes: 6 additions & 4 deletions src/providers/index.ts
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
];
101 changes: 101 additions & 0 deletions src/providers/walletconnectNotify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import fetch from 'node-fetch';
import { capture } from '@snapshot-labs/snapshot-sentry';
import db from '../helpers/mysql';
import { getSpace, getProposal } from '../helpers/utils';

const WALLETCONNECT_NOTIFY_SERVER_URL = 'https://notify.walletconnect.com';
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 ?? ''
devceline marked this conversation as resolved.
Show resolved Hide resolved
};

export async function queryWalletconnectSubscribers() {
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) {
console.error('[WalletConnect] Failed to fetch subscribers');
return [];
}
}

export async function crossReferencesSubscribers(internalSubscribers: string[]) {
const walletconnectSubscribers = await queryWalletconnectSubscribers();
const crossReferencedSubscribers = new Array(internalSubscribers.length);
const invalidAddresses = new Array(internalSubscribers.length / 4);

for (const internalSubscriber of internalSubscribers) {
if (walletconnectSubscribers.includes(internalSubscriber)) {
crossReferencedSubscribers.push(walletconnectSubscribers);
} else {
invalidAddresses.push(internalSubscriber);
}
}

if (invalidAddresses.length) {
const err = `[WalletConnect] there are ${invalidAddresses.length} addresses that are not subscribed through WalletConnect`;
capture(err);
}

return crossReferencesSubscribers;
}

export async function sendNotification(notification, accounts) {
const notifyUrl = `${WALLETCONNECT_NOTIFY_SERVER_URL}/${WALLETCONNECT_PROJECT_ID}/notify`;

const body = {
accounts,
Copy link
Contributor

Choose a reason for hiding this comment

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

accounts has a max length of 500, so we need to call /notify in a loop.

Also has a rate limit of 2 per second so you may need to add some artificial timing e.g. with date math and await new Promise(resolve => setTimeout(resolve, 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.

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);
}
}

async function formatMessage(event, proposal) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
async function formatMessage(event, proposal) {
function formatMessage(event, proposal) {

const space = await getSpace(event.space);
if (!space) return null;

switch (event.event) {
case 'proposal/created':
return {
title: proposal.title,
body: `A new proposal has been created for ${space.name}`,
url: `${proposal.link}?app=walletconnect`,
icon: ``,
type: 'proposal_update'
};
default:
return null;
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function send(event, proposal, subscribers) {
const crossReferencedSubscribers = await crossReferencesSubscribers(subscribers);
const notificationMessage = await formatMessage(event, proposal);
devceline marked this conversation as resolved.
Show resolved Hide resolved
await sendNotification(notificationMessage, crossReferencedSubscribers);
}
36 changes: 35 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down