Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
ChaituVR authored Nov 10, 2023
2 parents 676ccaf + 4fb365f commit b2cdc99
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 1 deletion.
30 changes: 30 additions & 0 deletions src/strategies/hats-protocol-hat-ids/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# hats-protocol-hat-ids

Grants voting power based on if voter has a set of specified hat (IP based).

Here is an example of parameters:

```json
{
"address": "0x3bc1A0Ad72417f2d411118085256fC53CBdDd137",
"hatIds": "[\"68\"]"
}
```

or

```json
{
"address": "0x3bc1A0Ad72417f2d411118085256fC53CBdDd137",
"hatIds": "[\"68\", \"68.1\"]"
}
```

or

```json
{
"address": "0x3bc1A0Ad72417f2d411118085256fC53CBdDd137",
"hatIds": "[\"68\", \"68.1\", \"68.1.1\"]"
}
```
26 changes: 26 additions & 0 deletions src/strategies/hats-protocol-hat-ids/examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"name": "Example query",
"strategy": {
"name": "hats-protocol-hat-ids",
"params": {
"address": "0x3bc1A0Ad72417f2d411118085256fC53CBdDd137",
"hatIds": [
"68",
"68.1",
"68.1.1",
"68.1.1.1"
]
}
},
"network": "5",
"addresses": [
"0xc4f6578c24c599F195c0758aD3D4861758d703A3",
"0xa6aF0566EF4eF7E8f38913f69d4e55c06F00A5aC",
"0x00e7332F9Cd4C05a0645AC959Fb1Be60ec24F94f",
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x4D6Ed22Ed0850384622EF129932aE29D27a89eD3"
],
"snapshot": 10015682
}
]
259 changes: 259 additions & 0 deletions src/strategies/hats-protocol-hat-ids/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import { subgraphRequest } from '../../utils';
import { getAddress } from '@ethersproject/address';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import { multicall } from '../../utils';

export const author = 'hotmanics';
export const version = '1.0.0';

const abi = [
'function isWearerOfHat(address _user, uint256 _hatId) external view returns (bool isWearer)'
];

async function subgraphRequestHats({ url, snapshot, treeIp }) {

let treeHex = treeIdDecimalToHex(treeIp);

const params = {
tree: {
__args: {
id: treeHex
},
id: true,
hats: {
id: true,
wearers: {
id: true
}
}
}
};

if (snapshot !== 'latest') {
// @ts-ignore
params.tree.__args.block = { number: snapshot };
}
const result = await subgraphRequest(url, params);
return result;
}

export async function strategy(
space,
network,
provider,
addresses,
options,
snapshot
): Promise<Record<string, number>> {
const blockTag = typeof snapshot === 'number' ? snapshot : 'latest';

//This strategy currently enforces that all hatIds passed in are from the same tree.
for (let i = 0; i < options.hatIds.length; i++) {
let lhs = treeIpFromIp(options.hatIds[i]);

for (let j = 0; j < options.hatIds.length; j++) {
let rhs = treeIpFromIp(options.hatIds[j]);
if (lhs !== rhs) {
throw Error("You can only use hats from the same tree!");
}
}
}

//all hatIds are assumed to be from the same tree, set selectedTree to any, and continue.
let selectedTree = treeIpFromIp(options.hatIds[0]);

const request = {
url: getActiveNetworkSubgraphURL(network),
snapshot,
treeIp: selectedTree
}

const result = await subgraphRequestHats(request);

let validHats: any[] = [];

for (let j = 0; j < options.hatIds.length; j++) {
for (let i = 0; i < result.tree.hats.length; i++) {

let hatIpHex = HatIpToHex(options.hatIds[j]);

if (hatIpHex === result.tree.hats[i].id) {
validHats.push(result.tree.hats[i]);
break;
}
}
}

let wearersInAddresses = <any>[];

addresses.forEach((address) => {
const wearer = checkIfExists(address, validHats);
wearersInAddresses = wearersInAddresses.concat(wearer);
});

const multi = new Multicaller(network, provider, abi, { blockTag });

wearersInAddresses.forEach((wearer) => {
multi.call(wearer.address, options.address, 'isWearerOfHat', [
wearer.address,
wearer.hat
]);
});

const multiResult = await multi.execute();

const myObj = {};

wearersInAddresses.forEach((wearer) => {
myObj[wearer.address] = 0;
for (const result of multiResult) {
if (wearer.address === result.address) {
myObj[wearer.address] = 1;
break;
}
}
});

return myObj;
}

function getActiveNetworkSubgraphURL(network) {

let url;

switch (network) {
case '1':
url = 'https://api.thegraph.com/subgraphs/name/hats-protocol/hats-v1-ethereum';
break;
case '10':
url = 'https://api.thegraph.com/subgraphs/name/hats-protocol/hats-v1-optimism';
break;
case '5':
url = 'https://api.thegraph.com/subgraphs/name/hats-protocol/hats-v1-goerli';
break;
case '137':
url = 'https://api.thegraph.com/subgraphs/name/hats-protocol/hats-v1-polygon';
break;
case '100':
url = 'https://api.thegraph.com/subgraphs/name/hats-protocol/hats-v1-gnosis-chain';
break;
case '42161':
url = 'https://api.thegraph.com/subgraphs/name/hats-protocol/hats-v1-arbitrum';
break;
}

return url;
}

function checkIfExists(address, hats) {
const addressWithHats = <any>[];
hats.forEach((hat) => {
hat.wearers.forEach((wearer) => {
if (getAddress(wearer.id) === address) {
const addressWithHat = {
address: getAddress(wearer.id),
hat: BigInt(hat.id)
};
addressWithHats.push(addressWithHat);
}
});
});
return addressWithHats;
}

function treeIdDecimalToHex(treeId: number): string {
return "0x" + treeId.toString(16).padStart(8, "0");
}

function HatIpToHex(hatIp) {
let observedChunk = hatIp;

const sections: Number[] = [];

while (true) {
if (observedChunk.indexOf(".") === -1) {
let section = observedChunk.substring(0, observedChunk.length);
sections.push(Number(section));
break;
}

let section = observedChunk.substring(0, observedChunk.indexOf("."));
observedChunk = observedChunk.substring(observedChunk.indexOf(".") + 1, observedChunk.length);

sections.push(Number(section));
}

let constructedResult = "0x";

for (let i = 0; i < sections.length; i++) {
let hex = sections[i].toString(16);

if (i === 0) {
constructedResult += hex.padStart(10 - hex.length, "0");
} else {
constructedResult += hex.padStart(5 - hex.length, "0");
}

}

constructedResult = constructedResult.padEnd(66, "0");
return constructedResult;
}

function treeIpFromIp(hatIp) {
let treeIp;

if (hatIp.indexOf(".") === -1)
treeIp = hatIp;
else
treeIp = hatIp.substring(0, hatIp.indexOf("."));

return Number(treeIp);
}

class Multicaller {
public network: string;
public provider: StaticJsonRpcProvider;
public abi: any[];
public options: any = {};
public calls: any[] = [];
public paths: any[] = [];

constructor(
network: string,
provider: StaticJsonRpcProvider,
abi: any[],
options?
) {
this.network = network;
this.provider = provider;
this.abi = abi;
this.options = options || {};
}

call(path, address, fn, params?): Multicaller {
this.calls.push([address, fn, params]);
this.paths.push(path);
return this;
}

async execute(): Promise<any> {
const obj = <any>[];
const result = await multicall(
this.network,
this.provider,
this.abi,
this.calls,
this.options
);
result.forEach((r, i) => {
obj.push({
address: this.paths[i],
value: r
});
});
this.calls = [];
this.paths = [];
return obj;
}
}
34 changes: 34 additions & 0 deletions src/strategies/hats-protocol-hat-ids/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Strategy",
"definitions": {
"Strategy": {
"title": "Strategy",
"type": "object",
"properties": {
"address": {
"type": "string",
"title": "Contract address",
"examples": [
"e.g. 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984"
],
"pattern": "^0x[a-fA-F0-9]{40}$",
"minLength": 42,
"maxLength": 42
},
"hatIds": {
"type": "array",
"title": "Hat Ids",
"examples": [
"e.g. [\"68\", \"68.1\", \"68.1.1\", \"68.1.1.1\""
]
}
},
"required": [
"address",
"hatIds"
],
"additionalProperties": false
}
}
}
4 changes: 3 additions & 1 deletion src/strategies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ import * as stationScoreIfBadge from './station-score-if-badge';
import * as stationConstantIfBadge from './station-constant-if-badge';
import * as mangroveStationQVScaledToMGV from './mangrove-station-qv-scaled-to-mgv';
import * as floki from './floki';
import * as hatsProtocolHatIds from "./hats-protocol-hat-ids";

const strategies = {
'cap-voting-power': capVotingPower,
Expand Down Expand Up @@ -954,7 +955,8 @@ const strategies = {
'station-score-if-badge': stationScoreIfBadge,
'station-constant-if-badge': stationConstantIfBadge,
'mangrove-station-qv-scaled-to-mgv': mangroveStationQVScaledToMGV,
floki
floki,
'hats-protocol-hat-ids': hatsProtocolHatIds
};

Object.keys(strategies).forEach(function (strategyName) {
Expand Down

0 comments on commit b2cdc99

Please sign in to comment.