diff --git a/frontend/components/config.tsx b/frontend/components/config.tsx index dc799b63..3d3a150b 100644 --- a/frontend/components/config.tsx +++ b/frontend/components/config.tsx @@ -24,9 +24,6 @@ export function Config(props: ConfigProps) { ? Number(hypercertClient.config.chain.id) : undefined; - console.log(chainId); - console.log("Config: ", hypercertClient.config); - const data: ConfigData = { domain: DOMAIN, chainId: chainId, diff --git a/frontend/package.json b/frontend/package.json index 7c9186ee..15dc1a26 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,7 +25,7 @@ "@graphprotocol/client-cli": "^2.2.16", "@hypercerts-org/contracts": "0.9.0", "@hypercerts-org/observabletreemap": "workspace: *", - "@hypercerts-org/sdk": "1.0.0-alpha.6", + "@hypercerts-org/sdk": "1.0.0-alpha.8", "@mui/icons-material": "^5.11.9", "@mui/material": "^5.11.2", "@mui/x-date-pickers": "^5.0.12", diff --git a/graph/tests/.latest.json b/graph/tests/.latest.json index 9bf8a349..67836539 100644 --- a/graph/tests/.latest.json +++ b/graph/tests/.latest.json @@ -1,4 +1,4 @@ { "version": "0.6.0", - "timestamp": 1699280746839 + "timestamp": 1699397492586 } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 203d74fe..b518a718 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -304,7 +304,7 @@ importers: version: 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.33)(react@18.2.0) '@graphprotocol/client-cli': specifier: ^2.2.16 - version: 2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) + version: 2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) '@hypercerts-org/contracts': specifier: 0.9.0 version: 0.9.0(typescript@5.1.6) @@ -312,8 +312,8 @@ importers: specifier: 'workspace: *' version: link:../vendor/observabletreemap '@hypercerts-org/sdk': - specifier: 1.0.0-alpha.6 - version: 1.0.0-alpha.6(@envelop/core@3.0.6)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3)(zod@3.22.4) + specifier: 1.0.0-alpha.8 + version: 1.0.0-alpha.8(@envelop/core@3.0.6)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3)(zod@3.22.4) '@mui/icons-material': specifier: ^5.11.9 version: 5.14.15(@mui/material@5.14.15)(@types/react@18.2.33)(react@18.2.0) @@ -565,7 +565,7 @@ importers: specifier: 0.8.11 version: 0.8.11 '@openzeppelin/merkle-tree': - specifier: ^1.0.4 + specifier: ^1.0.5 version: 1.0.5 '@whatwg-node/fetch': specifier: ^0.9.13 @@ -5757,24 +5757,6 @@ packages: '@trufflesuite/bigint-buffer': 1.1.9 dev: true - /@graphprotocol/client-add-source-name@1.0.20(@graphql-mesh/types@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): - resolution: {integrity: sha512-JJ++BVg4fhNCbLej105uHpabZesLsCSo9p43ZKSTT1VUdbuZtarzyIHC3uUmbvCfWQMVTCJEBZGx4l41oooOiw==} - peerDependencies: - '@graphql-mesh/types': ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 - '@graphql-tools/delegate': ^9.0.32 - '@graphql-tools/utils': ^9.2.1 - '@graphql-tools/wrap': ^9.4.2 - graphql: ^15.2.0 || ^16.0.0 - dependencies: - '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) - '@graphql-tools/utils': 9.2.1(graphql@16.8.1) - '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) - graphql: 16.8.1 - lodash: 4.17.21 - tslib: 2.6.2 - dev: false - /@graphprotocol/client-add-source-name@1.0.20(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): resolution: {integrity: sha512-JJ++BVg4fhNCbLej105uHpabZesLsCSo9p43ZKSTT1VUdbuZtarzyIHC3uUmbvCfWQMVTCJEBZGx4l41oooOiw==} peerDependencies: @@ -5812,7 +5794,7 @@ packages: tslib: 2.6.2 dev: true - /@graphprotocol/client-auto-pagination@1.1.18(@graphql-mesh/types@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): + /@graphprotocol/client-auto-pagination@1.1.18(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1): resolution: {integrity: sha512-p8eEyeBcqxCXLxC7CNgIhLSCd7bjiKToKnrwYPShVb26gIG2JdAmD3/mpjuR+QaMA4chN/EO5t+TGvq6KnFx9g==} peerDependencies: '@graphql-mesh/types': ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 @@ -5821,7 +5803,7 @@ packages: '@graphql-tools/wrap': ^9.4.2 graphql: ^15.2.0 || ^16.0.0 dependencies: - '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@graphql-tools/wrap': 9.4.2(graphql@16.8.1) @@ -5849,15 +5831,15 @@ packages: tslib: 2.6.2 dev: true - /@graphprotocol/client-auto-type-merging@1.0.25(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1): + /@graphprotocol/client-auto-type-merging@1.0.25(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1): resolution: {integrity: sha512-kpiX2s804mpP3EVL0EdJfxeHWBTdg6SglIyEvSZ5T1OWyGDeMhr19D+gVIAlo22/PiBUkBDd0JfqppLsliPZ1A==} peerDependencies: '@graphql-mesh/types': ^0.78.0 || ^0.79.0 || ^0.80.0 || ^0.81.0 || ^0.82.0 || ^0.83.0 || ^0.84.0 || ^0.85.0 || ^0.89.0 || ^0.90.0 || ^0.91.0 || ^0.93.0 '@graphql-tools/delegate': ^9.0.32 graphql: ^15.2.0 || ^16.0.0 dependencies: - '@graphql-mesh/transform-type-merging': 0.93.1(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/transform-type-merging': 0.93.1(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) graphql: 16.8.1 tslib: 2.6.2 @@ -5907,19 +5889,19 @@ packages: tslib: 2.6.2 dev: true - /@graphprotocol/client-cli@2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): + /@graphprotocol/client-cli@2.2.22(@babel/core@7.23.2)(@envelop/core@3.0.6)(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6): resolution: {integrity: sha512-PIi8rFibYZVup+0jb08399RmbGF1ZrqUe6RXzLtKZBT57OWIMWwsFvdJyUAdr8Y8f0rrMn6A+Oy4nP1lf3hc1g==} hasBin: true peerDependencies: graphql: ^15.2.0 || ^16.0.0 dependencies: - '@graphprotocol/client-add-source-name': 1.0.20(@graphql-mesh/types@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) - '@graphprotocol/client-auto-pagination': 1.1.18(@graphql-mesh/types@0.93.2)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) - '@graphprotocol/client-auto-type-merging': 1.0.25(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) + '@graphprotocol/client-add-source-name': 1.0.20(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) + '@graphprotocol/client-auto-pagination': 1.1.18(@graphql-mesh/types@0.95.8)(@graphql-tools/delegate@9.0.35)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(graphql@16.8.1) + '@graphprotocol/client-auto-type-merging': 1.0.25(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) '@graphprotocol/client-block-tracking': 1.0.14(@graphql-tools/delegate@9.0.35)(graphql@16.8.1) '@graphprotocol/client-polling-live': 1.1.1(@envelop/core@3.0.6)(@graphql-tools/merge@8.4.2)(graphql@16.8.1) '@graphql-mesh/cli': 0.82.35(@babel/core@7.23.2)(@types/node@18.18.7)(graphql-tag@2.12.6)(graphql@16.8.1)(react-native@0.72.6) - '@graphql-mesh/graphql': 0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/graphql': 0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2) graphql: 16.8.1 tslib: 2.6.2 transitivePeerDependencies: @@ -6420,7 +6402,7 @@ packages: graphql: 16.8.1 path-browserify: 1.0.1 - /@graphql-mesh/graphql@0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.93.1)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2): + /@graphql-mesh/graphql@0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-1G2/1jkl1VPWhsZsUBwFQI5d9OxxEc+CMxy5ef0qI2WEXqIocOxMhEY53cc+tCSbuXR99rxos+KD/8Z6ZasaOQ==} peerDependencies: '@graphql-mesh/cross-helpers': ^0.3.4 @@ -6432,10 +6414,10 @@ packages: tslib: ^2.4.0 dependencies: '@graphql-mesh/cross-helpers': 0.3.4(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(react-native@0.72.6) - '@graphql-mesh/store': 0.93.1(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/string-interpolation': 0.4.4(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.93.2(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/url-loader': 7.17.18(@types/node@18.18.7)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) @@ -6663,7 +6645,7 @@ packages: graphql: 16.8.1 tslib: 2.6.2 - /@graphql-mesh/runtime@0.96.13(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + /@graphql-mesh/runtime@0.96.13(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-eZIW/gdEVLvCLEEae8e3lny7d89CFfDyu0Z0xu4yVEdYeVpG9Ki2mDYFHztusIIkZikecvdsoM9MZX6LYcPOkg==} engines: {node: '>=16.0.0'} peerDependencies: @@ -6679,8 +6661,8 @@ packages: '@envelop/graphql-jit': 8.0.1(@envelop/core@5.0.0)(graphql@16.8.1) '@graphql-mesh/cross-helpers': 0.3.4(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(react-native@0.72.6) '@graphql-mesh/string-interpolation': 0.5.3(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.93.2(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/batch-delegate': 9.0.0(graphql@16.8.1) '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) '@graphql-tools/executor': 1.2.0(graphql@16.8.1) @@ -6739,6 +6721,26 @@ packages: graphql: 16.8.1 tslib: 2.6.2 + /@graphql-mesh/store@0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-29lpMcvqS1DM9alUOCyj6he2V7ZzG/DZxkerRefT8Mo5FexwJZI3LeI0YHNSY9Cq0x8KzRoH1TWcTTN/1PDRRw==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@graphql-mesh/cross-helpers': ^0.4.1 + '@graphql-mesh/types': ^0.95.8 + '@graphql-mesh/utils': ^0.95.8 + '@graphql-tools/utils': ^9.2.1 || ^10.0.0 + graphql: '*' + tslib: ^2.4.0 + dependencies: + '@graphql-inspector/core': 5.0.1(graphql@16.8.1) + '@graphql-mesh/cross-helpers': 0.3.4(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(react-native@0.72.6) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-tools/utils': 9.2.1(graphql@16.8.1) + graphql: 16.8.1 + tslib: 2.6.2 + dev: false + /@graphql-mesh/store@0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-29lpMcvqS1DM9alUOCyj6he2V7ZzG/DZxkerRefT8Mo5FexwJZI3LeI0YHNSY9Cq0x8KzRoH1TWcTTN/1PDRRw==} engines: {node: '>=16.0.0'} @@ -6783,22 +6785,6 @@ packages: lodash.get: 4.4.2 tslib: 2.6.2 - /@graphql-mesh/transform-type-merging@0.93.1(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(graphql@16.8.1)(tslib@2.6.2): - resolution: {integrity: sha512-CUrqCMaEqO1LDusv59UPqmQju3f+LpEGxFu7CydMiIvbfKDDDrf8+dF3OVU7d/ZOMRxB6hR80JsQF0SVeXPCOQ==} - peerDependencies: - '@graphql-mesh/types': ^0.93.1 - '@graphql-mesh/utils': ^0.93.1 - graphql: '*' - tslib: ^2.4.0 - dependencies: - '@graphql-mesh/types': 0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.93.2(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) - '@graphql-tools/stitching-directives': 2.3.34(graphql@16.8.1) - graphql: 16.8.1 - tslib: 2.6.2 - dev: false - /@graphql-mesh/transform-type-merging@0.93.1(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-CUrqCMaEqO1LDusv59UPqmQju3f+LpEGxFu7CydMiIvbfKDDDrf8+dF3OVU7d/ZOMRxB6hR80JsQF0SVeXPCOQ==} peerDependencies: @@ -6808,12 +6794,11 @@ packages: tslib: ^2.4.0 dependencies: '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-tools/delegate': 9.0.35(graphql@16.8.1) '@graphql-tools/stitching-directives': 2.3.34(graphql@16.8.1) graphql: 16.8.1 tslib: 2.6.2 - dev: true /@graphql-mesh/types@0.93.2(@graphql-mesh/store@0.93.1)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-113DuJzmR7aj2EMnLPu33ktCe5k7+Mk0BxFfmQViUH+mkr6i4JMsWvPKs9dTODSYuSuwvAZ90Vw2l3QyMrbFVA==} @@ -6870,6 +6855,30 @@ packages: tiny-lru: 8.0.2 tslib: 2.6.2 + /@graphql-mesh/utils@0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): + resolution: {integrity: sha512-gH2/kXvxMHVWMX8DppIIZpFfSUaoKDJ6eQHFoAAsdabGE+vLtVk0OEYqMGVGtD/8ZDFa/P6CmwXc6hBzoLY6Kg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@graphql-mesh/cross-helpers': ^0.4.1 + '@graphql-mesh/types': ^0.95.8 + '@graphql-tools/utils': ^9.2.1 || ^10.0.0 + graphql: '*' + tslib: ^2.4.0 + dependencies: + '@graphql-mesh/cross-helpers': 0.3.4(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(react-native@0.72.6) + '@graphql-mesh/string-interpolation': 0.5.3(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-tools/delegate': 10.0.3(graphql@16.8.1) + '@graphql-tools/utils': 9.2.1(graphql@16.8.1) + '@whatwg-node/fetch': 0.9.14 + dset: 3.1.3 + graphql: 16.8.1 + js-yaml: 4.1.0 + lodash.get: 4.4.2 + lodash.topath: 4.5.2 + tiny-lru: 11.2.3 + tslib: 2.6.2 + /@graphql-mesh/utils@0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2): resolution: {integrity: sha512-gH2/kXvxMHVWMX8DppIIZpFfSUaoKDJ6eQHFoAAsdabGE+vLtVk0OEYqMGVGtD/8ZDFa/P6CmwXc6hBzoLY6Kg==} engines: {node: '>=16.0.0'} @@ -7519,8 +7528,8 @@ packages: - utf-8-validate dev: false - /@hypercerts-org/sdk@1.0.0-alpha.6(@envelop/core@3.0.6)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3)(zod@3.22.4): - resolution: {integrity: sha512-WnZJfKGGqaQAPasvKjAuQ1eyWJT8kfY5Z445SjeFU8xWDelLRGuwaMT1w+B3e16YbZFQXBr/eB/dabMVb9ZPrQ==} + /@hypercerts-org/sdk@1.0.0-alpha.8(@envelop/core@3.0.6)(@graphql-tools/delegate@9.0.35)(@graphql-tools/merge@8.4.2)(@graphql-tools/utils@9.2.1)(@graphql-tools/wrap@9.4.2)(@types/node@18.18.7)(node-fetch@3.3.2)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2)(typescript@5.1.6)(uint8arraylist@2.4.3)(zod@3.22.4): + resolution: {integrity: sha512-T44kiplKi4qJQFlnn9Ac8KSOyWeGdglaGjMxhYZzqCEhqWPYYOscm6Q6qu2/X+HDhKpO68MbJPnCp88ajpVAOg==} dependencies: '@ethereum-attestation-service/eas-sdk': 1.2.2-beta.0(ts-node@10.9.1)(typescript@5.1.6) '@ethersproject/abstract-signer': 5.7.0 @@ -7531,10 +7540,10 @@ packages: '@graphql-mesh/graphql': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(@types/node@18.18.7)(graphql@16.8.1)(react-dom@18.2.0)(react@18.2.0)(tslib@2.6.2) '@graphql-mesh/http': 0.96.14(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/runtime@0.96.13)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/merger-bare': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/runtime': 0.96.13(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.93.2)(@graphql-mesh/utils@0.93.2)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/runtime': 0.96.13(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/store': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-mesh/utils@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-mesh/types': 0.95.8(@graphql-mesh/store@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) - '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.4.1)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) + '@graphql-mesh/utils': 0.95.8(@graphql-mesh/cross-helpers@0.3.4)(@graphql-mesh/types@0.95.8)(@graphql-tools/utils@9.2.1)(graphql@16.8.1)(tslib@2.6.2) '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1) '@hypercerts-org/contracts': 0.8.11 '@openzeppelin/merkle-tree': 1.0.5 diff --git a/sdk/README.md b/sdk/README.md index 55f4adb5..40bf074b 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -24,15 +24,13 @@ import { HypercertClient } from "@hypercerts-org/sdk"; ```js const client = new HypercertClient({ - chainId: 5, - provider, - signer, + chain: { id: 5 } // required nftStorageToken, web3StorageToken, }); ``` -> **Note** If there's no `signer`, `provider`, `nftStorageToken` or `web3StorageToken` provided, the client will run in +> **Note** If there's no `walletClient`, `nftStorageToken` or `web3StorageToken` provided, the client will run in > [read-only mode](#read-only-mode) 4. Use the client object to interact with the Hypercert network. @@ -60,78 +58,36 @@ environment variables for your NFT.storage and web3.storage API keys in your .en ## Config -The SDK will try to determine the `DEFAULT_CHAIN_ID` and use that to inform the configuration. We allow for `overrides` -when creating the SDK by passing configuration variables. Finally, when not defaults or overrides are found, we check -the environment variables. +HypercertClientConfig is a configuration object used when initializing a new instance of the HypercertClient. It allows +you to customize the client by setting your own providers or deployments. At it's simplest, you only need to provide +`chain.id` to initalize the client in `readonly` mode. + +| Field | Type | Description | +| --------------------------- | ------- | ---------------------------------------------------------------------------------------------- | +| `chain` | Object | Partial configuration for the blockchain network. | +| `contractAddress` | String | The address of the deployed contract. | +| `graphUrl` | String | The URL to the subgraph that indexes the contract events. Override for localized testing. | +| `graphName` | String | The name of the subgraph. | +| `nftStorageToken` | String | The API token for NFT.storage. | +| `web3StorageToken` | String | The API token for Web3.storage. | +| `easContractAddress` | String | The address of the EAS contract. | +| `publicClient` | Object | The PublicClient is inherently read-only and is used for reading data from the blockchain. | +| `walletClient` | Object | The WalletClient is used for signing and sending transactions. | +| `unsafeForceOverrideConfig` | Boolean | Boolean to force the use of overridden values. | +| `readOnly` | Boolean | Boolean to assert if the client is in read-only mode. | +| `readOnlyReason` | String | Reason for read-only mode. This is optional and can be used for logging or debugging purposes. | ### Read-only mode The SDK client will be in read-only mode if any of the following conditions are true: -- The client was initialized without a signer or provider. -- The client was initialized with a provider but not a signer. -- The client was initialized with a signer but not a provider. +- The client was initialized without a walletprovider. - The contract address is not set. - The storage layer is in read-only mode. If any of these conditions are true, the readonly property of the HypercertClient instance will be set to true, and a warning message will be logged indicating that the client is in read-only mode. -### Defaults - -The constants.ts file defines various constants that are used throughout the Hypercert system. Here's a brief -explanation of each constant: - -`DEFAULT_CHAIN_ID`: This constant defines the default chain ID to use if no chain ID is specified. In this case, the -default chain ID is set to 5, which corresponds to the Goerli testnet. - -Based on `DEFAULT_CHAIN_ID` the SDK will select a `DEPLOYMENT`. - -`DEPLOYMENTS`: This constant defines the deployments that are managed by the Hypercert system. Each Deployment object -contains information about a specific deployment, including the chain ID, chain name, contract address, and graph name. - -For example: - -```json -{ - "5": { - "chainId": 5, - "chainName": "goerli", - "contractAddress": "0x822F17A9A5EeCFd66dBAFf7946a8071C265D1d07", - "graphName": "hypercerts-testnet" - } -} -``` - -### Client config properties - -| \| Property | Type | Description | -| ------------------ | -------------------- | -------------------------------------- | -| `chainId` | `number` | The chain ID of the network to use. | -| `chainName` | `string` | The name of the network to use. | -| `contractAddress` | `string` | The address of the Hypercert contract. | -| `rpcUrl` | `string` | The URL of the RPC endpoint to use. | -| `graphName` | `string` | The name of the Gsubgraph to use. | -| `provider` | `providers.Provider` | A custom provider to use. | -| `signer` | `Signer` | A custom signer to use. | -| `nftStorageToken` | `string` | Your NFT.storage API key. | -| `web3StorageToken` | `string` | Your web3.storage API key. | - -### Environment variables - -To determine the missing configuration values the SDK defaults to the following environment variables: - -| Environment Variable | Description | -| -------------------------------- | --------------------------------------------------------------------------------------------------- | -| `DEFAULT_CHAIN_ID` | Specifies the default chain ID to use if no chain ID is specified. | -| `CONTRACT_ADDRESS` | Specifies the contract address to use for the Hypercert protocol. | -| `RPC_URL` | Specifies the RPC URL to use for the evm-compatible network. | -| `PRIVATE_KEY` | Specifies the private key to use for signing transactions. | -| `NFT_STORAGE_TOKEN` | Specifies the NFT.storage API token to use for storing Hypercert metadata. | -| `NEXT_PUBLIC_NFT_STORAGE_TOKEN` | Specifies the NFT.storage API token to use for storing Hypercert metadata in a Next.js application. | -| `WEB3_STORAGE_TOKEN` | Specifies the Web3.storage API token to use for storing Hypercert data. | -| `NEXT_PUBLIC_WEB3_STORAGE_TOKEN` | Specifies the Web3.storage API token to use for storing Hypercert data in a Next.js application. | - ### Logging The logger for the SDK uses the log level based on the value of the LOG_LEVEL environment variable. The log level @@ -147,7 +103,7 @@ HypercertIndexer, and HypercertMinter classes, respectively. ```js const { client: { storage }, -} = new HypercertClient({}); +} = new HypercertClient({ chain: { id: 5 } }); ``` The `storage` is a utility class that provides methods for storing and retrieving Hypercert metadata on IPFS and @@ -156,7 +112,7 @@ NFT.storage. It is used by the HypercertClient to store metadata when creating n ```js const { client: { indexer }, -} = new HypercertClient({}); +} = new HypercertClient({ chain: { id: 5 } }); ``` The `indexer` is a utility class that provides methods for indexing and searching Hypercerts based on various criteria. @@ -165,7 +121,7 @@ It is used by the HypercertClient to retrieve event-based data via the subgraph ```js const { client: { contract }, -} = new HypercertClient({}); +} = new HypercertClient({ chain: { id: 5 } }); ``` Finally we have a `contract` that provides methods for interacting with the HypercertMinter smart contract. It is used diff --git a/sdk/package.json b/sdk/package.json index 91b39d68..ac49caee 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@hypercerts-org/sdk", - "version": "1.0.0-alpha.6", + "version": "1.0.0-alpha.8", "description": "SDK for hypercerts protocol", "repository": "git@github.com:hypercerts-org/hypercerts.git", "author": "Hypercerts team", @@ -38,7 +38,7 @@ "@graphql-mesh/utils": "^0.95.7", "@graphql-typed-document-node/core": "^3.2.0", "@hypercerts-org/contracts": "0.8.11", - "@openzeppelin/merkle-tree": "^1.0.4", + "@openzeppelin/merkle-tree": "^1.0.5", "@whatwg-node/fetch": "^0.9.13", "ajv": "^8.11.2", "axios": "^1.2.2", diff --git a/sdk/src/client.ts b/sdk/src/client.ts index 63b24c01..aeef85cc 100644 --- a/sdk/src/client.ts +++ b/sdk/src/client.ts @@ -25,18 +25,13 @@ import { validateAllowlist, validateMetaData, verifyMerkleProof, verifyMerklePro * It encapsulates the logic for storage, evaluation, indexing, and wallet interactions, abstracting the complexity and providing a simple API for users. * The client is read-only if the storage is read-only (no nft.storage/web3.storage keys) or if no walletClient was found. * - * Here's an example of how to create a new instance of `HypercertClient`: - * - * ```typescript + * @example * const config: Partial = { - * id: 5 + * chain: {id: 5}, * }; - * * const client = new HypercertClient(config); - * ``` * - * @param config - Hypercerts client configuration - * @dev Creates a Hypercerts client instance + * @param {Partial} config - The configuration options for the client. */ export default class HypercertClient implements HypercertClientInterface { readonly _config; @@ -50,7 +45,11 @@ export default class HypercertClient implements HypercertClientInterface { /** * Creates a new instance of the `HypercertClient` class. - * @param config The configuration options for the client. + * + * This constructor takes a `config` parameter that is used to configure the client. The `config` parameter should be a `HypercertClientConfig` object. If the public client cannot be connected, it throws a `ClientError`. + * + * @param {Partial} config - The configuration options for the client. + * @throws {ClientError} Will throw a `ClientError` if the public client cannot be connected. */ constructor(config: Partial) { this._config = getConfig(config); @@ -110,19 +109,26 @@ export default class HypercertClient implements HypercertClientInterface { } /** - * Mint a Hypercert claim - * @dev Mints a Hypercert claim with the given metadata, total units and transfer restrictions - * @param metaData - Hypercert metadata - * @param totalUnits - Total number of units for the Hypercert - * @param transferRestriction - Transfer restrictions for the Hypercert - * @returns Contract transaction + * Mints a new claim. + * + * This method first validates the provided metadata using the `validateMetaData` function. If the metadata is invalid, it throws a `MalformedDataError`. + * It then stores the metadata on IPFS using the `storeMetadata` method of the storage client. + * After that, it simulates a contract call to the `mintClaim` function with the provided parameters and the stored metadata CID to validate the transaction. + * Finally, it submits the request using the `submitRequest` method. + * + * @param {HypercertMetadata} metaData - The metadata for the claim. + * @param {bigint} totalUnits - The total units for the claim. + * @param {TransferRestrictions} transferRestriction - The transfer restrictions for the claim. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {MalformedDataError} Will throw a `MalformedDataError` if the provided metadata is invalid. */ mintClaim = async ( metaData: HypercertMetadata, totalUnits: bigint, transferRestriction: TransferRestrictions, overrides?: SupportedOverrides, - ) => { + ): Promise<`0x${string}`> => { const { account } = this.getWallet(); // validate metadata @@ -146,14 +152,20 @@ export default class HypercertClient implements HypercertClientInterface { }; /** - * Create a Hypercert claim with an allowlist - * @dev Mints a Hypercert claim with the given metadata, total units, transfer restrictions and allowlist - * @notice The total number of units in the allowlist must match the total number of units for the Hypercert - * @param allowList - Allowlist for the Hypercert - * @param metaData - Hypercert metadata - * @param totalUnits - Total number of units for the Hypercert - * @param transferRestriction - Transfer restrictions for the Hypercert - * @returns Contract transaction + * Creates an allowlist. + * + * This method first validates the provided allowlist and metadata using the `validateAllowlist` and `validateMetaData` functions respectively. If either is invalid, it throws a `MalformedDataError`. + * It then creates an allowlist from the provided entries and stores it on IPFS using the `storeData` method of the storage client. + * After that, it stores the metadata (including the CID of the allowlist) on IPFS using the `storeMetadata` method of the storage client. + * Finally, it simulates a contract call to the `createAllowlist` function with the provided parameters and the stored metadata CID, and submits the request using the `submitRequest` method. + * + * @param {AllowlistEntry[]} allowList - The entries for the allowlist. + * @param {HypercertMetadata} metaData - The metadata for the claim. + * @param {bigint} totalUnits - The total units for the claim. + * @param {TransferRestrictions} transferRestriction - The transfer restrictions for the claim. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {MalformedDataError} Will throw a `MalformedDataError` if the provided allowlist or metadata is invalid. */ createAllowlist = async ( allowList: AllowlistEntry[], @@ -161,7 +173,7 @@ export default class HypercertClient implements HypercertClientInterface { totalUnits: bigint, transferRestriction: TransferRestrictions, overrides?: SupportedOverrides, - ) => { + ): Promise<`0x${string}`> => { const { account } = this.getWallet(); // validate allowlist @@ -196,14 +208,24 @@ export default class HypercertClient implements HypercertClientInterface { }; /** - * Split a Hypercert's unit into multiple claims with the given fractions - * @dev Submit the ID of the claim to split and new fraction values. - * @notice The sum of the fractions must be equal to the total units of the claim - * @param fractionId - Hypercert claim id - * @param newFractions - Fractions of the Hypercert claim to split - * @returns Contract transaction + * Splits a fraction into multiple fractions. + * + * This method first retrieves the wallet client and account using the `getWallet` method. It then retrieves the owner and total units of the fraction using the `ownerOf` and `unitsOf` methods of the read contract. + * If the fraction is not owned by the account, it throws a `ClientError`. + * It then checks if the sum of the provided fractions is equal to the total units of the fraction. If not, it throws a `ClientError`. + * Finally, it simulates a contract call to the `splitFraction` function with the provided parameters and the account, and submits the request using the `submitRequest` method. + * + * @param {bigint} fractionId - The ID of the fraction to split. + * @param {bigint[]} fractions - The fractions to split the fraction into. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {ClientError} Will throw a `ClientError` if the fraction is not owned by the account or if the sum of the fractions is not equal to the total units of the fraction. */ - splitFractionUnits = async (fractionId: bigint, fractions: bigint[], overrides?: SupportedOverrides) => { + splitFractionUnits = async ( + fractionId: bigint, + fractions: bigint[], + overrides?: SupportedOverrides, + ): Promise<`0x${string}`> => { const { account } = this.getWallet(); const readContract = getContract({ @@ -234,12 +256,18 @@ export default class HypercertClient implements HypercertClientInterface { }; /** - * Merge multiple Hypercert claims fractions into one - * @dev Merges multiple Hypercert claims into one - * @param fractionIds - Hypercert claim ids - * @returns Contract transaction + * Merges multiple fractions into a single fraction. + * + * This method first retrieves the wallet client and account using the `getWallet` method. It then retrieves the owner of each fraction using the `ownerOf` method of the read contract. + * If any of the fractions are not owned by the account, it throws a `ClientError`. + * It then simulates a contract call to the `mergeFractions` function with the provided parameters and the account, and submits the request using the `submitRequest` method. + * + * @param {bigint[]} fractionIds - The IDs of the fractions to merge. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {ClientError} Will throw a `ClientError` if any of the fractions are not owned by the account. */ - mergeFractionUnits = async (fractionIds: bigint[], overrides?: SupportedOverrides) => { + mergeFractionUnits = async (fractionIds: bigint[], overrides?: SupportedOverrides): Promise<`0x${string}`> => { const { account } = this.getWallet(); const readContract = getContract({ @@ -272,12 +300,18 @@ export default class HypercertClient implements HypercertClientInterface { }; /** - * Burn a Hypercert claim by providing the claim id - * @dev Burns a Hypercert claim - * @param fractionId - Hypercert claim id - * @returns Contract transaction + * Burns a claim fraction. + * + * This method first retrieves the wallet client and account using the `getWallet` method. It then retrieves the owner of the claim using the `ownerOf` method of the read contract. + * If the claim is not owned by the account, it throws a `ClientError`. + * It then simulates a contract call to the `burnFraction` function with the provided parameters and the account, and submits the request using the `submitRequest` method. + * + * @param {bigint} claimId - The ID of the claim to burn. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {ClientError} Will throw a `ClientError` if the claim is not owned by the account. */ - burnClaimFraction = async (claimId: bigint, overrides?: SupportedOverrides) => { + burnClaimFraction = async (claimId: bigint, overrides?: SupportedOverrides): Promise<`0x${string}`> => { const { account } = this.getWallet(); const readContract = getContract({ @@ -303,13 +337,18 @@ export default class HypercertClient implements HypercertClientInterface { }; /** - * Mint a Hypercert claim fraction from an allowlist. - * @dev Verifies the claim proof and mints the claim fraction - * @notice If known, provide the root for client side verification - * @param claimId - Hypercert claim id - * @param units - Number of units to mint - * @param proof - Merkle proof for the claim - * @returns Contract transaction + * Mints a claim fraction from an allowlist. + * + * This method first retrieves the wallet client and account using the `getWallet` method. It then verifies the provided proof using the `verifyMerkleProof` function. If the proof is invalid, it throws an `InvalidOrMissingError`. + * It then simulates a contract call to the `mintClaimFromAllowlist` function with the provided parameters and the account, and submits the request using the `submitRequest` method. + * + * @param {bigint} claimId - The ID of the claim to mint. + * @param {bigint} units - The units of the claim to mint. + * @param {(Hex | ByteArray)[]} proof - The proof for the claim. + * @param {Hex | ByteArray} [root] - The root of the proof. If provided, it is used to verify the proof. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {InvalidOrMissingError} Will throw an `InvalidOrMissingError` if the proof is invalid. */ mintClaimFractionFromAllowlist = async ( claimId: bigint, @@ -317,7 +356,7 @@ export default class HypercertClient implements HypercertClientInterface { proof: (Hex | ByteArray)[], root?: Hex | ByteArray, overrides?: SupportedOverrides, - ) => { + ): Promise<`0x${string}`> => { const { account } = this.getWallet(); //verify the proof using the OZ merkle tree library @@ -343,14 +382,18 @@ export default class HypercertClient implements HypercertClientInterface { }; /** - * Batch mints a claim fraction from an allowlist - * @param claimIds Array of the IDs of the claims to mint fractions for. - * @param units Array of the number of units for each fraction. - * @param proofs Array of Merkle proofs for the allowlists. - * @returns A Promise that resolves to the transaction receipt - * @note The length of the arrays must be equal. - * @note The order of the arrays must be equal. - * @returns A Promise that resolves to the transaction receipt + * Mints multiple claim fractions from allowlists in a batch. + * + * This method first retrieves the wallet client and account using the `getWallet` method. If the roots are provided, it verifies each proof using the `verifyMerkleProofs` function. If any of the proofs are invalid, it throws an `InvalidOrMissingError`. + * It then simulates a contract call to the `batchMintClaimsFromAllowlists` function with the provided parameters and the account, and submits the request using the `submitRequest` method. + * + * @param {bigint[]} claimIds - The IDs of the claims to mint. + * @param {bigint[]} units - The units of each claim to mint. + * @param {(Hex | ByteArray)[][]} proofs - The proofs for each claim. + * @param {(Hex | ByteArray)[]} [roots] - The roots of each proof. If provided, they are used to verify the proofs. + * @param {SupportedOverrides} [overrides] - Optional overrides for the contract call. + * @returns {Promise<`0x${string}`>} A promise that resolves to the transaction hash. + * @throws {InvalidOrMissingError} Will throw an `InvalidOrMissingError` if any of the proofs are invalid. */ batchMintClaimFractionsFromAllowlists = async ( claimIds: bigint[], @@ -358,7 +401,7 @@ export default class HypercertClient implements HypercertClientInterface { proofs: (Hex | ByteArray)[][], roots?: (Hex | ByteArray)[], overrides?: SupportedOverrides, - ) => { + ): Promise<`0x${string}`> => { const { account } = this.getWallet(); //verify the proof using the OZ merkle tree library @@ -412,8 +455,17 @@ export default class HypercertClient implements HypercertClientInterface { return { walletClient: this._walletClient, account: this._walletClient.account }; }; + /** + * Submits a contract request. + * + * This method submits a contract request using the `writeContract` method of the wallet client. If the request fails, it throws a `ClientError`. + * + * @param {any} request - The contract request to submit. + * @returns {Promise<`0x${string}`>} A promise that resolves to the hash of the submitted request. + * @throws {ClientError} Will throw a `ClientError` if the request fails. + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - private submitRequest = async (request: any) => { + private submitRequest = async (request: any): Promise<`0x${string}`> => { const hash = this._walletClient?.writeContract(request); if (!hash) { diff --git a/sdk/src/constants.ts b/sdk/src/constants.ts index 05307613..deebf21c 100644 --- a/sdk/src/constants.ts +++ b/sdk/src/constants.ts @@ -20,7 +20,7 @@ const DEPLOYMENTS: { [key in SupportedChainIds]: Partial } = { } as const, 42220: { contractAddress: "0x16ba53b74c234c870c61efc04cd418b8f2865959", - graphName: "hypercerts-arbitrum", + graphName: "hypercerts-celo", graphUrl: `${DEFAULT_GRAPH_BASE_URL}/hypercerts-celo`, }, 11155111: { diff --git a/sdk/src/index.ts b/sdk/src/index.ts index abe5436b..4de15b87 100644 --- a/sdk/src/index.ts +++ b/sdk/src/index.ts @@ -32,9 +32,9 @@ export * from "./types"; export * from "./validator"; /** - * Formatters + * Utils */ -export * from "./utils/formatter"; +export * from "./utils"; /** * Graph diff --git a/sdk/src/indexer.ts b/sdk/src/indexer.ts index c3438f45..337355e5 100644 --- a/sdk/src/indexer.ts +++ b/sdk/src/indexer.ts @@ -4,7 +4,15 @@ import { HypercertClientConfig, HypercertIndexerInterface, QueryParams } from ". /** * A class that provides indexing functionality for Hypercerts. - * @class HypercertIndexer + * + * This class implements the `HypercertIndexerInterface` and provides methods for retrieving claims by owner and by ID. It uses the Graph client for indexing. + * Because of the autogenerated Graph client packed with the SDK, this class is not recommended for custom Graph deployments. + * + * @property {GraphClient} _graphClient - The Graph client used by the indexer. + * + * @example + * const indexer = new HypercertIndexer({ graphUrl: 'your-graph-url', graphName: 'your-graph-name' }); + * const claims = await indexer.claimsByOwner('your-address'); */ export default class HypercertIndexer implements HypercertIndexerInterface { /** The Graph client used by the indexer. */ diff --git a/sdk/src/storage.ts b/sdk/src/storage.ts index 19bab0e8..fb2606e2 100644 --- a/sdk/src/storage.ts +++ b/sdk/src/storage.ts @@ -1,4 +1,3 @@ -import axios from "axios"; //eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { CIDString, NFTStorage } from "nft.storage"; @@ -16,11 +15,20 @@ import { } from "./types"; import logger from "./utils/logger"; import { getNftStorageToken, getWeb3StorageToken } from "./utils/config"; - -const getCid = (cidOrIpfsUri: string) => cidOrIpfsUri.replace("ipfs://", ""); +import fetchers from "./utils/fetchers"; /** * A class that provides storage functionality for Hypercerts. + * + * This class implements the `HypercertStorageInterface` and provides methods for storing and retrieving Hypercerts. It uses the NFT Storage and Web3 Storage APIs for storage, and can be configured to be read-only. + * + * @property {boolean} readonly - Whether the storage is read-only. If true, the storage methods will not perform any write operations. + * @property {NFTStorage} nftStorageClient - The NFT Storage client used for storing and retrieving Hypercerts. + * @property {Web3Storage} web3StorageClient - The Web3 Storage client used for storing and retrieving Hypercerts. + * + * @example + * const storage = new HypercertsStorage({ nftStorageToken: 'your-nft-storage-token', web3StorageToken: 'your-web3-storage-token' }); + * const metadata = await storage.getMetadata('your-hypercert-id'); */ export default class HypercertsStorage implements HypercertStorageInterface { /** Whether the storage is read-only. */ @@ -32,7 +40,10 @@ export default class HypercertsStorage implements HypercertStorageInterface { /** * Creates a new instance of the `HypercertsStorage` class. - * @param overrides The configuration overrides for the storage. + * + * This constructor takes an optional `overrides` parameter that can be used to override the default configuration. If the NFT Storage or Web3 Storage API keys are missing or invalid, the storage will be read-only. + * + * @param {Partial} overrides - The configuration overrides for the storage. */ constructor(overrides: Partial) { const nftStorageToken = getNftStorageToken(overrides); @@ -56,32 +67,17 @@ export default class HypercertsStorage implements HypercertStorageInterface { } } - getFromIPFS = async (cidOrIpfsUri: string, timeout = 10000) => { - const nftStorageGatewayLink = this.getNftStorageGatewayUri(cidOrIpfsUri); - const web3StorageGatewayLink = this.getWeb3StorageGatewayUri(cidOrIpfsUri); - logger.debug(`Getting metadata ${cidOrIpfsUri} at ${nftStorageGatewayLink}`); - - const res = await axios.get(nftStorageGatewayLink, { timeout }).catch(() => { - logger.debug(`${nftStorageGatewayLink} timed out.`); - logger.debug(`Getting metadata ${cidOrIpfsUri} at ${web3StorageGatewayLink}`); - return axios.get(web3StorageGatewayLink, { timeout }); - }); - - if (!res || !res.data) { - throw new StorageError(`Failed to get ${cidOrIpfsUri}`); - } - - return res.data; - }; - /** - * Stores metadata for a Hypercert. - * @param data The metadata to store. - * @returns A Promise that resolves to the CID of the stored metadata. - * @throws A `StorageError` if the storage client is not configured. - * @throws A `MalformedDataError` if the metadata is invalid. - * @notice Because we pay for storage quotas, this data is stored best effort. - * If you are using our default keys, we may delete older data if we hit our storage quota. + * Stores Hypercert metadata using the NFT Storage client. + * + * This method first checks if the storage is read-only or if the NFT Storage client is not configured. If either of these conditions is true, it throws a `StorageError`. + * It then validates the provided metadata using the `validateMetaData` function. If the metadata is invalid, it throws a `MalformedDataError`. + * If the metadata is valid, it creates a new Blob from the metadata and stores it using the NFT Storage client. If the storage operation fails, it throws a `StorageError`. + * + * @param {HypercertMetadata} data - The Hypercert metadata to store. This should be an object that conforms to the HypercertMetadata type. + * @returns {Promise} A promise that resolves to the CID of the stored metadata. + * @throws {StorageError} Will throw a `StorageError` if the storage is read-only, if the NFT Storage client is not configured, or if the storage operation fails. + * @throws {MalformedDataError} Will throw a `MalformedDataError` if the provided metadata is invalid. */ public async storeMetadata(data: HypercertMetadata): Promise { if (this.readonly || !this.nftStorageClient) { @@ -107,29 +103,37 @@ export default class HypercertsStorage implements HypercertStorageInterface { } /** - * Gets metadata for a Hypercert. - * @param cidOrIpfsUri The CID or IPFS URI of the metadata to get. - * @returns A Promise that resolves to the metadata. - * @throws A `StorageError` if the storage client is not configured or the metadata cannot be retrieved. - * @throws A `MalformedDataError` if the metadata is invalid. E.g. unknown schema + * Retrieves Hypercert metadata from IPFS using the provided CID or IPFS URI. + * + * This method first retrieves the data from IPFS using the `getFromIPFS` function. It then validates the retrieved data using the `validateMetaData` function. If the data is invalid, it throws a `MalformedDataError`. + * If the data is valid, it returns the data as a `HypercertMetadata` object. + * + * @param {string} cidOrIpfsUri - The CID or IPFS URI of the metadata to retrieve. + * @returns {Promise} A promise that resolves to the retrieved metadata. + * @throws {MalformedDataError} Will throw a `MalformedDataError` if the retrieved data is invalid. */ public async getMetadata(cidOrIpfsUri: string): Promise { - const res = await this.getFromIPFS(cidOrIpfsUri); + const res = await fetchers.getFromIPFS(cidOrIpfsUri); const validation = validateMetaData(res); if (!validation.valid) { throw new MalformedDataError(`Invalid metadata at ${cidOrIpfsUri}`, { errors: validation.errors }); } - return res; + return validation.data as HypercertMetadata; } /** - * Stores arbitrary data in Web3 storage. - * @param data The data to store. - * @returns A Promise that resolves to the CID of the stored data. - * @throws A `StorageError` if the storage client is not configured. - * @notice Even though web3.storage takes a list of files, we'll assume we're only storing 1 JSON blob. + * Stores data using the Web3 Storage client. + * + * This method first checks if the storage is read-only or if the Web3 Storage client is not configured. If either of these conditions is true, it throws a `StorageError`. + * It then creates a new Blob from the provided data and stores it using the Web3 Storage client. If the storage operation fails, it throws a `StorageError`. + * + * @param {unknown} data - The data to store. This can be any type of data. + * @returns {Promise} A promise that resolves to the CID of the stored data. + * @throws {StorageError} Will throw a `StorageError` if the storage is read-only, if the Web3 Storage client is not configured, or if the storage operation fails. + * + * @remarks Even though web3.storage takes a list of files, we'll assume we're only storing 1 JSON blob. * Because we pay for storage quotas, this data is stored best effort. * If you are using our default keys, we may delete older data if we hit our storage quota. */ @@ -150,12 +154,18 @@ export default class HypercertsStorage implements HypercertStorageInterface { } /** - * Gets arbitrary data from Web3 storage. - * @param cidOrIpfsUri The CID or IPFS URI of the data to get. - * @returns A Promise that resolves to the data. - * @throws A `StorageError` if the storage client is not configured or the data cannot be retrieved. + * Retrieves data from IPFS using the provided CID or IPFS URI. + * + * This method first retrieves the data from IPFS using the `getFromIPFS` function. It then parses the retrieved data as JSON and returns it. + * + * @param {string} cidOrIpfsUri - The CID or IPFS URI of the data to retrieve. + * @returns {Promise} A promise that resolves to the retrieved data. + * @throws {FetchError} Will throw a `FetchError` if the retrieval operation fails. + * @throws {MalformedDataError} Will throw a `MalformedDataError` if the retrieved data is not a single file. + * + * @remarkts Note: The original implementation using the Web3 Storage client is currently commented out due to issues with upstream repos. This will be replaced once those issues are resolved. */ - public async getData(cidOrIpfsUri: string) { + public async getData(cidOrIpfsUri: string): Promise { /** // Using the default web3.storage client is not working in upstream repos. Needs further testing. const cid = getCid(cidOrIpfsUri); @@ -183,18 +193,6 @@ export default class HypercertsStorage implements HypercertStorageInterface { */ // TODO: replace current temporary fix of just using NFT.Storage IPFS gateway - const res = await this.getFromIPFS(cidOrIpfsUri); - - return res; + return await fetchers.getFromIPFS(cidOrIpfsUri); } - - getNftStorageGatewayUri = (cidOrIpfsUri: string) => { - const NFT_STORAGE_IPFS_GATEWAY = "https://nftstorage.link/ipfs/{cid}"; - return NFT_STORAGE_IPFS_GATEWAY.replace("{cid}", getCid(cidOrIpfsUri)); - }; - - getWeb3StorageGatewayUri = (cidOrIpfsUri: string) => { - const WEB3_STORAGE_IPFS_GATEWAY = "https://w3s.link/ipfs/{cid}"; - return WEB3_STORAGE_IPFS_GATEWAY.replace("{cid}", getCid(cidOrIpfsUri)); - }; } diff --git a/sdk/src/types/hypercerts.ts b/sdk/src/types/hypercerts.ts index 9851d633..82f03c55 100644 --- a/sdk/src/types/hypercerts.ts +++ b/sdk/src/types/hypercerts.ts @@ -1,8 +1,10 @@ /** - * Transfer restrictions for Hypercerts matching the definitions in the Hypercerts protocol - * @dev AllowAll: All transfers are allowed - * @dev DisallowAll: All transfers are disallowed - * @dev FromCreatorOnly: Only the creator can transfer the Hypercert + * Represents the possible transfer restrictions of a claim matching the hypercerts protocol. + * + * @typedef {Object} TransferRestrictions + * @property {number} AllowAll - Represents no restrictions on the transfer of the claim. + * @property {number} DisallowAll - Represents complete restriction on the transfer of the claim. + * @property {number} FromCreatorOnly - Represents that the claim can only be transferred by its creator. */ export const TransferRestrictions = { AllowAll: 0, @@ -13,9 +15,11 @@ export const TransferRestrictions = { export type TransferRestrictions = (typeof TransferRestrictions)[keyof typeof TransferRestrictions]; /** - * Allowlist entry for Hypercerts matching the definitions in the Hypercerts protocol - * @param address - Address of the recipient - * @param units - Number of units allocated to the recipient + * Represents an entry in an allowlist. + * + * @typedef {Object} AllowlistEntry + * @property {string} address - The address of the entry. + * @property {bigint} units - The units associated with the entry. */ export type AllowlistEntry = { address: string; diff --git a/sdk/src/utils/adapters.ts b/sdk/src/utils/adapters.ts index 9c33a336..6108b01a 100644 --- a/sdk/src/utils/adapters.ts +++ b/sdk/src/utils/adapters.ts @@ -3,6 +3,19 @@ import { PublicClient, HttpTransport, WalletClient } from "viem"; import logger from "./logger"; import { Signer, TypedDataSigner } from "@ethersproject/abstract-signer"; +/** + * This function converts a `PublicClient` instance to an ethers.js `Provider` to faciliate compatibility between ethers and viem. + * + * It extracts the chain and transport from the `PublicClient` and creates a network object. + * If no chain is found in the `PublicClient`, it logs a warning and stops the signature request. + * If the transport type is "fallback", it creates a `FallbackProvider` with multiple transports. + * Otherwise, it creates a `JsonRpcProvider` with a single transport. + * + * Ref: https://viem.sh/docs/ethers-migration.html + * + * @param publicClient - The `PublicClient` instance to convert. + * @returns An ethers.js `Provider` instance, or `undefined` if no chain is found in the `PublicClient`. + */ export function publicClientToProvider(publicClient: PublicClient) { const { chain, transport } = publicClient; if (!chain) { @@ -23,6 +36,18 @@ export function publicClientToProvider(publicClient: PublicClient) { return new providers.JsonRpcProvider(transport.url, network); } +/** + * This function converts a `WalletClient` instance to an ethers.js `Signer` to faciliate compatibility between ethers and viem. + * + * It extracts the account, chain, and transport from the `WalletClient` and creates a network object. + * If no chain is found in the `WalletClient`, it logs a warning and stops the signature request. + * It then creates a `Web3Provider` with the transport and network, and gets a `Signer` from the provider using the account's address. + * + * Ref: https://viem.sh/docs/ethers-migration.html + * + * @param walletClient - The `WalletClient` instance to convert. + * @returns An ethers.js `Signer` instance, or `undefined` if no chain is found in the `WalletClient`. + */ export function walletClientToSigner(walletClient: WalletClient) { const { account, chain, transport } = walletClient; if (!chain) { diff --git a/sdk/src/utils/allowlist.ts b/sdk/src/utils/allowlist.ts new file mode 100644 index 00000000..634c5eab --- /dev/null +++ b/sdk/src/utils/allowlist.ts @@ -0,0 +1,46 @@ +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; +import fetchers from "./fetchers"; +import logger from "./logger"; + +const getMerkleTreeFromIPFS = async (cidOrIpfsUri: string) => { + const data = await fetchers.getFromIPFS(cidOrIpfsUri); + const allowlist = typeof data === "string" ? data : undefined; + + if (!allowlist) { + throw new Error(`Invalid allowlist at ${cidOrIpfsUri}`); + } + + const tree = StandardMerkleTree.load(JSON.parse(allowlist)); + + if (!tree) { + throw new Error(`Invalid allowlist at ${cidOrIpfsUri}`); + } + + return tree; +}; + +/** + * This function retrieves proofs from an allowlist. + * + * It fetches a Merkle tree from IPFS using a given CID or IPFS URI, then iterates over the tree to find an account. + * When the account is found, it generates a proof for that account and logs the account, index, and proof as debug. + * It returns the proof and the root of the Merkle tree. + * + * @param cidOrIpfsUri - The CID or IPFS URI to fetch the Merkle tree from. + * @param account - The account to find in the Merkle tree. + * @returns An object containing the proof for the account and the root of the Merkle tree. + * @throws Will throw an error if the Merkle tree cannot be fetched. + * @async + */ +const getProofsFromAllowlist = async (cidOrIpfsUri: string, account: `0x${string}`) => { + const tree = await getMerkleTreeFromIPFS(cidOrIpfsUri); + for (const [i, v] of tree.entries()) { + if (v[0] === account) { + const proof = tree.getProof(i); + logger.debug(`Found ${account} at index ${i} with proof ${proof}`); + return { proof, root: tree.root }; + } + } +}; + +export default { getProofsFromAllowlist }; diff --git a/sdk/src/utils/config.ts b/sdk/src/utils/config.ts index d8ad86d3..cbc9c70a 100644 --- a/sdk/src/utils/config.ts +++ b/sdk/src/utils/config.ts @@ -14,14 +14,25 @@ import { createPublicClient, http, isAddress } from "viem"; import { deployments } from "../../src"; /** - * Returns the configuration for the Hypercert client, based on the given overrides. - * @param config An object containing overrides for the default configuration. - * @returns The configuration for the Hypercert client. - * @throws An `ConfigurationError` if the `environment` in `config` is not a supported environment, or if the chain ID was not found. - * @dev 5, 10, 42220, 11155111 and "test", "production" are supported environments. - * Test and production merge the Graphs by environment, while the chain IDs are specific to the chain. + * Returns a configuration object for the Hypercert client. + * + * This function first retrieves the chain configuration, then checks if there are any overrides provided. If the `unsafeForceOverrideConfig` flag is set, + * it validates the overrides and uses them to create the base deployment configuration. If the flag is not set, it retrieves the deployment configuration + * for the provided chain ID or the default chain ID. It then merges the base deployment configuration with the overrides and the values retrieved from + * environment variables to create the final configuration object. If any required properties are missing, it logs a warning. + * + * Current supported chain IDs are: + * - 5: Goerli + * - 10: Optimism + * - 42220: Celo + * - 11155111: Sepolia + * + * @param {Partial} overrides - An object containing any configuration values to override. This should be a partial HypercertClientConfig object. + * @returns {Partial} The final configuration object for the Hypercert client. + * @throws {InvalidOrMissingError} Will throw an `InvalidOrMissingError` if the `unsafeForceOverrideConfig` flag is set but the required overrides are not provided. + * @throws {UnsupportedChainError} Will throw an `UnsupportedChainError` if the default configuration for the provided chain ID is missing. */ -export const getConfig = (overrides: Partial) => { +export const getConfig = (overrides: Partial): Partial => { // Get the chainId, first from overrides, then environment variables, then the constant const chain = getChainConfig(overrides); if (!chain) { diff --git a/sdk/src/utils/fetchers.ts b/sdk/src/utils/fetchers.ts new file mode 100644 index 00000000..41d9003a --- /dev/null +++ b/sdk/src/utils/fetchers.ts @@ -0,0 +1,47 @@ +import { StorageError } from "../types/errors"; +import logger from "./logger"; +import axios from "axios"; + +/** + * Fetches data from IPFS using either the NFT Storage gateway or the Web3 Storage gateway. + * + * This function attempts to fetch data from the NFT Storage gateway first. If the request times out, it then tries to fetch the data from the Web3 Storage gateway. + * If the data cannot be fetched from either gateway, it throws a `StorageError`. + * + * @param {string} cidOrIpfsUri - The CID or IPFS URI of the data to fetch. + * @param {number} [timeout=10000] - The timeout for the fetch request in milliseconds. Defaults to 10000ms. + * @returns {Promise} The data fetched from IPFS. + * @throws {StorageError} Will throw a `StoragjeError` if the data cannot be fetched from either gateway. + * @async + */ +const getFromIPFS = async (cidOrIpfsUri: string, timeout: number = 10000): Promise => { + const nftStorageGatewayLink = getNftStorageGatewayUri(cidOrIpfsUri); + const web3StorageGatewayLink = getWeb3StorageGatewayUri(cidOrIpfsUri); + logger.debug(`Getting metadata ${cidOrIpfsUri} at ${nftStorageGatewayLink}`); + + const res = await axios.get(nftStorageGatewayLink, { timeout }).catch(() => { + logger.debug(`${nftStorageGatewayLink} timed out.`); + logger.debug(`Getting metadata ${cidOrIpfsUri} at ${web3StorageGatewayLink}`); + return axios.get(web3StorageGatewayLink, { timeout }); + }); + + if (!res || !res.data) { + throw new StorageError(`Failed to get ${cidOrIpfsUri}`); + } + + return res.data; +}; + +const getCid = (cidOrIpfsUri: string) => cidOrIpfsUri.replace("ipfs://", ""); + +const getNftStorageGatewayUri = (cidOrIpfsUri: string) => { + const NFT_STORAGE_IPFS_GATEWAY = "https://nftstorage.link/ipfs/{cid}"; + return NFT_STORAGE_IPFS_GATEWAY.replace("{cid}", getCid(cidOrIpfsUri)); +}; + +const getWeb3StorageGatewayUri = (cidOrIpfsUri: string) => { + const WEB3_STORAGE_IPFS_GATEWAY = "https://w3s.link/ipfs/{cid}"; + return WEB3_STORAGE_IPFS_GATEWAY.replace("{cid}", getCid(cidOrIpfsUri)); +}; + +export default { getFromIPFS }; diff --git a/sdk/src/utils/index.ts b/sdk/src/utils/index.ts new file mode 100644 index 00000000..d4dccf7b --- /dev/null +++ b/sdk/src/utils/index.ts @@ -0,0 +1,7 @@ +import { walletClientToSigner, publicClientToProvider } from "./adapters"; + +import allowlist from "./allowlist"; +import fetchers from "./fetchers"; +import { formatHypercertData } from "./formatter"; + +export { walletClientToSigner, publicClientToProvider, allowlist, fetchers, formatHypercertData }; diff --git a/sdk/src/validator/index.ts b/sdk/src/validator/index.ts index e843fd84..dc592083 100644 --- a/sdk/src/validator/index.ts +++ b/sdk/src/validator/index.ts @@ -7,6 +7,7 @@ import metaDataSchema from "../resources/schema/metadata.json"; import { AllowlistEntry, DuplicateEvaluation, + EvaluationData, HypercertClaimdata, HypercertMetadata, MintingError, @@ -20,25 +21,33 @@ ajv.addSchema(claimDataSchema, "claimData"); ajv.addSchema(evaluationSchema, "evaluation.json"); /** - * The result of a validation. - * @property valid - Whether the data is valid. - * @property errors - A map of errors, where the key is the field that failed validation and the value is the error message. + * Represents the result of a validation operation. + * + * This type is used to return the result of validating data against a schema. It includes a `valid` flag that indicates + * whether the data is valid, and an `errors` object that contains any errors that occurred during validation. + * */ type ValidationResult = { + data: AllowlistEntry[] | EvaluationData | HypercertClaimdata | HypercertMetadata | unknown; valid: boolean; - errors: Record; + errors: Record; }; /** - * Validates the data for a simple text evaluation. - * @param data The data to validate. - * @returns A `ValidationResult` object indicating whether the data is valid and any errors that were found. + * Validates Hypercert metadata. + * + * This function uses the AJV library to validate the metadata. It first retrieves the schema for the metadata, + * then validates the data against the schema. If the schema is not found, it returns an error. If the data does not + * conform to the schema, it returns the validation errors. If the data is valid, it returns a success message. + * + * @param {unknown} data - The metadata to validate. This should be an object that conforms to the HypercertMetadata type. + * @returns {ValidationResult} An object that includes a validity flag and any errors that occurred during validation. */ -const validateMetaData = (data: HypercertMetadata): ValidationResult => { +const validateMetaData = (data: unknown): ValidationResult => { const schemaName = "metaData"; const validate = ajv.getSchema(schemaName); if (!validate) { - return { valid: false, errors: { schema: "Schema not found" } }; + return { data, valid: false, errors: { schema: "Schema not found" } }; } if (!validate(data)) { @@ -49,22 +58,27 @@ const validateMetaData = (data: HypercertMetadata): ValidationResult => { errors[key] = e.message; } } - return { valid: false, errors }; + return { data, valid: false, errors }; } - return { valid: true, errors: {} }; + return { data: data as HypercertMetadata, valid: true, errors: {} }; }; /** - * Validates the data for a simple text evaluation. - * @param data The data to validate. - * @returns A `ValidationResult` object indicating whether the data is valid and any errors that were found. + * Validates Hypercert claim data. + * + * This function uses the AJV library to validate the claim data. It first retrieves the schema for the claim data, + * then validates the data against the schema. If the schema is not found, it returns an error. If the data does not + * conform to the schema, it returns the validation errors. If the data is valid, it returns a success message. + * + * @param {unknown} data - The claim data to validate. This should be an object that conforms to the HypercertClaimdata type. + * @returns {ValidationResult} An object that includes a validity flag and any errors that occurred during validation. */ -const validateClaimData = (data: HypercertClaimdata): ValidationResult => { +const validateClaimData = (data: unknown): ValidationResult => { const schemaName = "claimData"; const validate = ajv.getSchema(schemaName); if (!validate) { - return { valid: false, errors: { schema: "Schema not found" } }; + return { data, valid: false, errors: { schema: "Schema not found" } }; } if (!validate(data)) { @@ -75,19 +89,23 @@ const validateClaimData = (data: HypercertClaimdata): ValidationResult => { errors[key] = e.message; } } - return { valid: false, errors }; + return { data, valid: false, errors }; } - return { valid: true, errors: {} }; + return { data: data as HypercertClaimdata, valid: true, errors: {} }; }; /** - * Validates the data for an allowlist. - * @param data The data to validate. - * @param units The total number of units in the allowlist. - * @returns A `ValidationResult` object indicating whether the data is valid and any errors that were found. + * Validates an array of allowlist entries. + * + * This function checks that the total units in the allowlist match the expected total units, that the total units are greater than 0, + * and that all addresses in the allowlist are valid Ethereum addresses. It returns an object that includes a validity flag and any errors that occurred during validation. + * + * @param {AllowlistEntry[]} data - The allowlist entries to validate. Each entry should be an object that includes an address and a number of units. + * @param {bigint} units - The expected total units in the allowlist. + * @returns {ValidationResult} An object that includes a validity flag and any errors that occurred during validation. The keys in the errors object are the names of the invalid properties, and the values are the error messages. */ -const validateAllowlist = (data: AllowlistEntry[], units: bigint) => { +const validateAllowlist = (data: AllowlistEntry[], units: bigint): ValidationResult => { const errors: Record = {}; const totalUnits = data.reduce((acc, curr) => acc + BigInt(curr.units.toString()), 0n); if (totalUnits != units) { @@ -105,18 +123,23 @@ const validateAllowlist = (data: AllowlistEntry[], units: bigint) => { errors["address"] = filteredAddresses.map((entry) => entry.address); } - return { valid: Object.keys(errors).length === 0, errors }; + return { data, valid: Object.keys(errors).length === 0, errors }; }; /** - * Validates the data for a duplicate evaluation. - * @param data The data to validate. - * @returns A `ValidationResult` object indicating whether the data is valid and any errors that were found. + * Validates duplicate evaluation data. + * + * This function uses the AJV library to validate the duplicate evaluation data. It first retrieves the schema for the duplicate evaluation data, + * then validates the data against the schema. If the schema is not found, it returns an error. If the data does not + * conform to the schema, it returns the validation errors. If the data is valid, it returns a success message. + * + * @param {DuplicateEvaluation} data - The duplicate evaluation data to validate. This should be an object that conforms to the DuplicateEvaluation type. + * @returns {ValidationResult} An object that includes a validity flag and any errors that occurred during validation. */ const validateDuplicateEvaluationData = (data: DuplicateEvaluation): ValidationResult => { const validate = ajv.getSchema("evaluation.json#/definitions/DuplicateEvaluation"); if (!validate) { - return { valid: false, errors: { schema: "Schema not found" } }; + return { data, valid: false, errors: { schema: "Schema not found" } }; } if (!validate(data)) { @@ -127,21 +150,26 @@ const validateDuplicateEvaluationData = (data: DuplicateEvaluation): ValidationR errors[key] = e.message; } } - return { valid: false, errors }; + return { data, valid: false, errors }; } - return { valid: true, errors: {} }; + return { data, valid: true, errors: {} }; }; /** - * Validates the data for a simple text evaluation. - * @param data The data to validate. - * @returns A `ValidationResult` object indicating whether the data is valid and any errors that were found. + * Validates simple text evaluation data against a predefined schema. + * + * This function uses the AJV library to validate the simple text evaluation data. It first retrieves the schema for the simple text evaluation data, + * then validates the data against the schema. If the schema is not found, it returns an error. If the data does not + * conform to the schema, it returns the validation errors. If the data is valid, it returns a success message. + * + * @param {SimpleTextEvaluation} data - The simple text evaluation data to validate. This should be an object that conforms to the SimpleTextEvaluation type. + * @returns {ValidationResult} An object that includes a validity flag and any errors that occurred during validation. */ const validateSimpleTextEvaluationData = (data: SimpleTextEvaluation): ValidationResult => { const validate = ajv.getSchema("evaluation.json#/definitions/SimpleTextEvaluation"); if (!validate) { - return { valid: false, errors: { schema: "Schema not found" } }; + return { data, valid: false, errors: { schema: "Schema not found" } }; } if (!validate(data)) { @@ -152,19 +180,23 @@ const validateSimpleTextEvaluationData = (data: SimpleTextEvaluation): Validatio errors[key] = e.message; } } - return { valid: false, errors }; + return { data, valid: false, errors }; } - return { valid: true, errors: {} }; + return { data, valid: true, errors: {} }; }; /** - * Verifies a Merkle proof for a given address and units. - * @param root The Merkle root hash to verify against. - * @param signerAddress The address to verify. - * @param units The units to verify. - * @param proof The Merkle proof to verify. - * @throws {MintingError} If the Merkle proof verification fails. + * Verifies a Merkle proof for a given root, signer address, units, and proof. + * + * This function first checks if the signer address is a valid Ethereum address. If it's not, it throws a `MintingError`. + * It then verifies the Merkle proof using the `StandardMerkleTree.verify` method. If the verification fails, it throws a `MintingError`. + * + * @param {string} root - The root of the Merkle tree. + * @param {string} signerAddress - The signer's Ethereum address. + * @param {bigint} units - The number of units. + * @param {string[]} proof - The Merkle proof to verify. + * @throws {MintingError} Will throw a `MintingError` if the signer address is invalid or if the Merkle proof verification fails. */ function verifyMerkleProof(root: string, signerAddress: string, units: bigint, proof: string[]): void { if (!isAddress(signerAddress)) { @@ -178,13 +210,16 @@ function verifyMerkleProof(root: string, signerAddress: string, units: bigint, p } /** - * Batch verifies Merkle proofs for multiple roots, units and proofs for a single address - * @param roots The Merkle root hashes to verify against. - * @param signerAddress The address to verify. - * @param units The units to verify. - * @param proofs The Merkle proofs to verify. - * @throws {MintingError} If the Merkle proof verification fails. - * @notice Wrapper around `verifyMerkleProof` to batch verify multiple proofs + * Verifies multiple Merkle proofs for given roots, a signer address, units, and proofs. + * + * This function first checks if the lengths of the roots, units, and proofs arrays are equal. If they're not, it throws a `MintingError`. + * It then iterates over the arrays and verifies each Merkle proof using the `verifyMerkleProof` function. If any verification fails, it throws a `MintingError`. + * + * @param {string[]} roots - The roots of the Merkle trees. + * @param {string} signerAddress - The signer's Ethereum address. + * @param {bigint[]} units - The numbers of units. + * @param {string[][]} proofs - The Merkle proofs to verify. + * @throws {MintingError} Will throw a `MintingError` if the lengths of the input arrays are not equal or if any Merkle proof verification fails. */ function verifyMerkleProofs(roots: string[], signerAddress: string, units: bigint[], proofs: string[][]) { if (roots.length !== units.length || units.length !== proofs.length) { diff --git a/sdk/test/storage/nft.storage.test.ts b/sdk/test/storage/nft.storage.test.ts index d5244a4d..c78d96a5 100644 --- a/sdk/test/storage/nft.storage.test.ts +++ b/sdk/test/storage/nft.storage.test.ts @@ -3,36 +3,34 @@ import { jest } from "@jest/globals"; // @ts-ignore import { NFTStorage } from "nft.storage"; -import HypercertsStorage from "../../src/storage.js"; -import { MalformedDataError } from "../../src/types/errors.js"; -import { HypercertMetadata } from "../../src/types/metadata.js"; -import { getFormattedMetadata, mockDataSets } from "../helpers.js"; +import HypercertsStorage from "../../src/storage"; +import { MalformedDataError } from "../../src/types/errors"; +import { HypercertMetadata } from "../../src/types/metadata"; +import { getFormattedMetadata, mockDataSets } from "../helpers"; +import fetchers from "../../src/utils/fetchers"; +import sinon from "sinon"; describe("NFT.Storage Client", () => { - const { hypercertData, hypercertMetadata } = mockDataSets; + const { hypercertMetadata } = mockDataSets; const storeBlobMock = jest.spyOn(NFTStorage.prototype, "storeBlob").mockImplementation((_: unknown, __?: unknown) => { return Promise.resolve(hypercertMetadata.cid); }); + const ipfsFetcherMock = sinon.stub(fetchers, "getFromIPFS"); + const storage = new HypercertsStorage({ nftStorageToken: process.env.NFT_STORAGE_TOKEN, web3StorageToken: process.env.WEB3_STORAGE_TOKEN, }); - jest.spyOn(storage, "getFromIPFS").mockImplementation((cid: string) => { - if (cid === hypercertMetadata.cid) return Promise.resolve(hypercertMetadata.data); - if (cid === hypercertData.cid) return Promise.resolve(hypercertData.data); - - return Promise.resolve("testData"); - }); - afterEach(() => { jest.clearAllMocks(); }); afterAll(() => { jest.resetAllMocks(); + sinon.resetBehavior(); }); /** @@ -44,6 +42,7 @@ describe("NFT.Storage Client", () => { }); it("Smoke test - get metadata", async () => { + ipfsFetcherMock.returns(Promise.resolve(hypercertMetadata.data)); const res = await storage.getMetadata(hypercertMetadata.cid); expect(res).toMatchObject(hypercertMetadata.data); @@ -65,6 +64,8 @@ describe("NFT.Storage Client", () => { it("Throws when trying to fetch incorrect metadata", async () => { const incorrectCID = "incorrect-cid"; + + ipfsFetcherMock.resolves({ data: "false" }); // storeData try { await storage.getMetadata(incorrectCID); diff --git a/sdk/test/storage/web3.storage.test.ts b/sdk/test/storage/web3.storage.test.ts index e5845098..a3d5af8e 100644 --- a/sdk/test/storage/web3.storage.test.ts +++ b/sdk/test/storage/web3.storage.test.ts @@ -5,6 +5,8 @@ import { Web3Storage } from "web3.storage"; import HypercertsStorage from "../../src/storage"; import { mockDataSets } from "../helpers"; +import fetchers from "../../src/utils/fetchers"; +import sinon from "sinon"; describe("Web3.Storage Client", () => { const { hypercertData, hypercertMetadata } = mockDataSets; @@ -13,24 +15,20 @@ describe("Web3.Storage Client", () => { return Promise.resolve(hypercertMetadata.cid); }); + const ipfsFetcherMock = sinon.stub(fetchers, "getFromIPFS"); + const storage = new HypercertsStorage({ nftStorageToken: process.env.NFT_STORAGE_TOKEN, web3StorageToken: process.env.WEB3_STORAGE_TOKEN, }); - jest.spyOn(storage, "getFromIPFS").mockImplementation((cid: string) => { - if (cid === hypercertMetadata.cid) return Promise.resolve(hypercertMetadata.data); - if (cid === hypercertData.cid) return Promise.resolve(hypercertData.data); - - return Promise.resolve("testData"); - }); - afterEach(() => { jest.clearAllMocks(); }); afterAll(() => { jest.resetAllMocks(); + sinon.resetBehavior(); }); /** @@ -42,6 +40,7 @@ describe("Web3.Storage Client", () => { }); it("Smoke test - get data", async () => { + ipfsFetcherMock.returns(Promise.resolve(hypercertData.data)); const res = await storage.getData(hypercertData.cid); expect(res).toMatchObject(hypercertData.data); diff --git a/sdk/test/utils/allowlist.test.ts b/sdk/test/utils/allowlist.test.ts new file mode 100644 index 00000000..61fa4f90 --- /dev/null +++ b/sdk/test/utils/allowlist.test.ts @@ -0,0 +1,48 @@ +import chai, { expect } from "chai"; +import chaiSubset from "chai-subset"; +import sinon from "sinon"; + +import { faker } from "@faker-js/faker"; +import { StandardMerkleTree } from "@openzeppelin/merkle-tree"; +import fetchers from "../../src/utils/fetchers"; +import allowlistUtils from "../../src/utils/allowlist"; + +chai.use(chaiSubset); + +const createAllowlist = async () => { + const allowlist = new Array(10).fill(0).map((_x) => ({ + address: faker.finance.ethereumAddress() as `0x${string}`, + units: 1n, + })); + + // create allowlist + const tuples = allowlist.map((p) => [p.address, p.units.toString()]); + const tree = StandardMerkleTree.of(tuples, ["address", "uint256"]); + + return { allowlist, tree }; +}; + +describe("Fetchers", () => { + const ipfsFetcherMock = sinon.stub(fetchers, "getFromIPFS"); + + afterEach(() => { + sinon.restore(); + }); + + it("Proof: should return valid proof and root", async () => { + const { allowlist, tree } = await createAllowlist(); + + const stub = ipfsFetcherMock.resolves(Promise.resolve(JSON.stringify(tree.dump()))); + + const res = await allowlistUtils.getProofsFromAllowlist("test", allowlist[0].address); + + expect(res).to.containSubset({ + proof: tree.getProof(0), + root: tree.root, + }); + + expect(res?.proof).to.deep.equal(tree.getProof(0)); + expect(res?.root).to.deep.equal(tree.root); + expect(stub.calledOnce).to.be.true; + }); +}); diff --git a/sdk/test/utils/fetchers.test.ts b/sdk/test/utils/fetchers.test.ts new file mode 100644 index 00000000..cd868b6b --- /dev/null +++ b/sdk/test/utils/fetchers.test.ts @@ -0,0 +1,37 @@ +import chai, { expect } from "chai"; +import chaiSubset from "chai-subset"; +import sinon from "sinon"; + +import fetchers from "../../src/utils/fetchers"; +import axios from "axios"; + +chai.use(chaiSubset); + +describe("Fetchers", () => { + afterEach(() => { + sinon.restore(); + }); + + it("IPFS: should return data from IPFS", async () => { + const validResponse = { data: "TEST_PASSED" }; + const axiosStub = sinon.stub(axios, "get").resolves(Promise.resolve(validResponse)); + + const res = await fetchers.getFromIPFS("test"); + expect(res).to.equal(validResponse.data); + expect(axiosStub.calledOnce).to.be.true; + }); + + it("IPFS: should try another endpoint after the first fails", async () => { + const validResponse = { data: "TEST_PASSED" }; + const axiosStub = sinon + .stub(axios, "get") + .onFirstCall() + .rejects() + .onSecondCall() + .resolves(Promise.resolve(validResponse)); + + const res = await fetchers.getFromIPFS("test"); + expect(res).to.equal(validResponse.data); + expect(axiosStub.calledTwice).to.be.true; + }); +});