mirror of
https://github.com/FlipsideCrypto/cosanostra.git
synced 2026-02-06 10:56:48 +00:00
Merge pull request #6 from FlipsideCrypto/5-feature-papers
feat: add paper support
This commit is contained in:
commit
ffa1da86d4
250
package-lock.json
generated
250
package-lock.json
generated
@ -15,9 +15,12 @@
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-query": "^3.39.2",
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^3.0.0"
|
||||
}
|
||||
@ -3147,6 +3150,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@remix-run/router": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz",
|
||||
"integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||
@ -5330,6 +5341,14 @@
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/big-integer": {
|
||||
"version": "1.6.51",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
|
||||
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
@ -5442,6 +5461,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/broadcast-channel": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
|
||||
"integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"detect-node": "^2.1.0",
|
||||
"js-sha3": "0.8.0",
|
||||
"microseconds": "0.2.0",
|
||||
"nano-time": "1.0.0",
|
||||
"oblivious-set": "1.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"unload": "2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||
@ -11414,6 +11448,11 @@
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
|
||||
"integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q=="
|
||||
},
|
||||
"node_modules/js-sha3": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
|
||||
},
|
||||
"node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -11760,6 +11799,26 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"node_modules/markdown-to-jsx": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
|
||||
"integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/match-sorter": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
|
||||
"integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"remove-accents": "0.4.2"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
|
||||
@ -11822,6 +11881,11 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/microseconds": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
|
||||
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
@ -11987,6 +12051,14 @@
|
||||
"multicast-dns": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/nano-time": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
|
||||
"integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
|
||||
"dependencies": {
|
||||
"big-integer": "^1.6.16"
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
@ -12235,6 +12307,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/oblivious-set": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
|
||||
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
|
||||
},
|
||||
"node_modules/obuf": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
|
||||
@ -14216,6 +14293,31 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-query": {
|
||||
"version": "3.39.2",
|
||||
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.2.tgz",
|
||||
"integrity": "sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"broadcast-channel": "^3.4.1",
|
||||
"match-sorter": "^6.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/tannerlinsley"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||
@ -14224,6 +14326,36 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz",
|
||||
"integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz",
|
||||
"integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==",
|
||||
"dependencies": {
|
||||
"@remix-run/router": "1.0.2",
|
||||
"react-router": "6.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8",
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@ -14460,6 +14592,11 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/remove-accents": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
|
||||
"integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
|
||||
},
|
||||
"node_modules/renderkid": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
|
||||
@ -15981,6 +16118,15 @@
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/unload": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
|
||||
"integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.6.2",
|
||||
"detect-node": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@ -19153,6 +19299,11 @@
|
||||
"source-map": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"@remix-run/router": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.0.2.tgz",
|
||||
"integrity": "sha512-GRSOFhJzjGN+d4sKHTMSvNeUPoZiDHWmRnXfzaxrqe7dE/Nzlc8BiMSJdLDESZlndM7jIUrZ/F4yWqVYlI0rwQ=="
|
||||
},
|
||||
"@rollup/plugin-babel": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
|
||||
@ -20792,6 +20943,11 @@
|
||||
"tryer": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"big-integer": {
|
||||
"version": "1.6.51",
|
||||
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz",
|
||||
"integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg=="
|
||||
},
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
@ -20887,6 +21043,21 @@
|
||||
"fill-range": "^7.0.1"
|
||||
}
|
||||
},
|
||||
"broadcast-channel": {
|
||||
"version": "3.7.0",
|
||||
"resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz",
|
||||
"integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"detect-node": "^2.1.0",
|
||||
"js-sha3": "0.8.0",
|
||||
"microseconds": "0.2.0",
|
||||
"nano-time": "1.0.0",
|
||||
"oblivious-set": "1.0.0",
|
||||
"rimraf": "3.0.2",
|
||||
"unload": "2.2.0"
|
||||
}
|
||||
},
|
||||
"browser-process-hrtime": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
|
||||
@ -25203,6 +25374,11 @@
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
|
||||
"integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q=="
|
||||
},
|
||||
"js-sha3": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
|
||||
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
|
||||
},
|
||||
"js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||
@ -25469,6 +25645,21 @@
|
||||
"tmpl": "1.0.5"
|
||||
}
|
||||
},
|
||||
"markdown-to-jsx": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz",
|
||||
"integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==",
|
||||
"requires": {}
|
||||
},
|
||||
"match-sorter": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz",
|
||||
"integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"remove-accents": "0.4.2"
|
||||
}
|
||||
},
|
||||
"mdn-data": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz",
|
||||
@ -25516,6 +25707,11 @@
|
||||
"picomatch": "^2.3.1"
|
||||
}
|
||||
},
|
||||
"microseconds": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz",
|
||||
"integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA=="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
@ -25629,6 +25825,14 @@
|
||||
"thunky": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"nano-time": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz",
|
||||
"integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==",
|
||||
"requires": {
|
||||
"big-integer": "^1.6.16"
|
||||
}
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
@ -25799,6 +26003,11 @@
|
||||
"es-abstract": "^1.19.1"
|
||||
}
|
||||
},
|
||||
"oblivious-set": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz",
|
||||
"integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw=="
|
||||
},
|
||||
"obuf": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
|
||||
@ -27051,11 +27260,38 @@
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"react-query": {
|
||||
"version": "3.39.2",
|
||||
"resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.2.tgz",
|
||||
"integrity": "sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"broadcast-channel": "^3.4.1",
|
||||
"match-sorter": "^6.0.2"
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A=="
|
||||
},
|
||||
"react-router": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.4.2.tgz",
|
||||
"integrity": "sha512-Rb0BAX9KHhVzT1OKhMvCDMw776aTYM0DtkxqUBP8dNBom3mPXlfNs76JNGK8wKJ1IZEY1+WGj+cvZxHVk/GiKw==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.0.2"
|
||||
}
|
||||
},
|
||||
"react-router-dom": {
|
||||
"version": "6.4.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.4.2.tgz",
|
||||
"integrity": "sha512-yM1kjoTkpfjgczPrcyWrp+OuQMyB1WleICiiGfstnQYo/S8hPEEnVjr/RdmlH6yKK4Tnj1UGXFSa7uwAtmDoLQ==",
|
||||
"requires": {
|
||||
"@remix-run/router": "1.0.2",
|
||||
"react-router": "6.4.2"
|
||||
}
|
||||
},
|
||||
"react-scripts": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||
@ -27238,6 +27474,11 @@
|
||||
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
|
||||
"integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog=="
|
||||
},
|
||||
"remove-accents": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
|
||||
"integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
|
||||
},
|
||||
"renderkid": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz",
|
||||
@ -28351,6 +28592,15 @@
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ=="
|
||||
},
|
||||
"unload": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz",
|
||||
"integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.6.2",
|
||||
"detect-node": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
|
||||
@ -10,9 +10,12 @@
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"markdown-to-jsx": "^7.1.7",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-helmet-async": "^1.3.0",
|
||||
"react-query": "^3.39.2",
|
||||
"react-router-dom": "^6.4.2",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^3.0.0"
|
||||
},
|
||||
|
||||
239
papers/subsidized-on-chain-public-goods.md
Normal file
239
papers/subsidized-on-chain-public-goods.md
Normal file
@ -0,0 +1,239 @@
|
||||
title:Subsidized On-Chain Public Goods
|
||||
date:Sep. 09, 2022
|
||||
author:danner* and CHANCE+
|
||||
description: Through the use of publicly contributable vaults purpose built to pay gas on behalf of users, blockchains can fund public goods with certainty that funds will not be misappropriated and without directly enriching grantees.
|
||||
---
|
||||
## Abstract
|
||||
Starting an initiative requires the raising of funds to design, produce, and distribute a product. In Web2, this was often through venture capitalists who acquired equity in the initiative and sought to profit from it. In Web3, we have seen a wider variety of funding models – with outcome based desires that vary from traditional private equity models.
|
||||
|
||||
Throughout 2021 and 2022, the blockchain industry has operated based on a diversified capital stack composed of venture capitalists, professional and amateur market makers, speculative retail traders, and treasury grant issuance. While much of that capital stack is familiar, treasury grant issuance is nuanced and accounts for a large amount of the capital directed towards those who create value for the network.
|
||||
|
||||
One of the most admirable and fascinating types of treasury grant issuance is retroactive public goods funding. Optimism has been a leader in this space, directing over $1 million generated from their sequencer to public goods. Retroactive public goods funding is the idea that grant distribution is made simpler by rewarding what was useful in the past rather than what might be useful in the future.
|
||||
|
||||
One of the more interesting uses of capital (public and private) has been to subsidize the costs of on-chain transaction fees for users. DAO tools, NFT product lines, etc; developers and businesses have found ways to improve end user experience by paying their fees for them. In traditional businesses, this may be viewed as a cost of customer acquisition and is often a teaser rate. While we have not seen crypto companies adjust their models yet, we must recognize that at scale, paying transaction fees on behalf of all users is not a maximal or even sustainable business model.
|
||||
|
||||
Through the use of publicly fundable vaults permissioned solely to pay transaction fees on behalf of users, grantors can fund public goods with certainty that funds will not be misappropriated and without directly enriching grantees. Use case specific applications surfacing highly flexible protocols and primitives may merit public goods funding from networks in which they are deployed. As highly usable and desirable implementations, these applications and protocols will be demanded by existing users and may potentially draw in new users to the product and the network it operates on. Vaults funded by blockchain foundations and network participants will be used to subsidize the usage of these public good applications.
|
||||
|
||||
The Badge Bound Forwarder primitive empowers subsidization of any on-chain product line.
|
||||
|
||||
## Full Spectrum Funding in Web3
|
||||
The Optimism Collective has made a commitment to fund the first public goods exit. With their focus on retroactive public goods funding, they wish to scour the market for teams and products that have created value for the wider network and to reward these teams.
|
||||
|
||||
Critically, retroactive public goods funding requires that the good has already created value.
|
||||
|
||||
While some developers may pursue retroactive public goods funding upon adoption by users, they may still need to raise up front capital required to develop and distribute their product.
|
||||
|
||||
Through a combination of venture capital and retroactive funding, developers may access capital at the beginning and end of their journey. As noted above, a portion of raised capital is often utilized by businesses to support the subsidization of transaction fees throughout the development and distribution of the product.
|
||||
|
||||
With the introduction of Badge Bound Forwarders, developers can access capital solely for the purpose of subsidizing on-chain transactions associated with their product for the first time.
|
||||
|
||||
1. Private capital may be raised to develop, deploy, and market via venture, grants, etc.
|
||||
2. Public funding can be accessed to ease user adoption via Badge Bound Forwarders.
|
||||
3. Public funding may be accessed via retroactive public goods funding
|
||||
|
||||
Badge Bound Forwarders may be utilized by for profit and non-profit teams depending on their unique goals and circumstances. This paper makes no claims about the feasibility of accessing various capital stacks but does assert that this new conduit creates a value flow from grantors to users and grantees without directly enriching grantees.
|
||||
|
||||
## Delegated Transaction Execution
|
||||
The Flow blockchain has been a leader in crypto consumer products. From CryptoKitties to NFL All Day and everything in between, Dapper has focused on end user experience from the beginning. One of the critical features of Dapper products on Flow is that all chain transaction fees are abstracted away from the end user. This experience is far superior and drives widespread adoption, but we have not seen it fully penetrate the EVM user experience.
|
||||
|
||||
Over the past year, a number of DAO tools have begun to adopt [delegated calls](https://medium.com/coinmonks/delegatecall-calling-another-contract-function-in-solidity-b579f804178c), which are transactions run through an external contract that carries the context of the sender. The use of delegated calls allows them to subsidize transactions for users.
|
||||
|
||||
For example, [MetricsDAO](https://dune.com/metricsdao/MetricsDAO) and other organizations use [Utopia](https://www.utopialabs.com/) and [Parcel](https://parcel.money/) to process transactions from a multisig without the need to pay gas costs. By offering this service, these tooling organizations are able to drive adoption at a much higher rate.
|
||||
|
||||
## The Code Driving Democratized Subsidization
|
||||
With the use of [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771) contracts can support secure native meta-transactions. One individual can build and sign a transaction while another executes and subsequently pays for the transaction cost. Deeper than that though, is the ability to forward transactions through a contract that opens the gateway to design space beyond just the processing of the transaction the Signer has built.
|
||||
|
||||
The steps are rather simple when broken down into clear steps:
|
||||
|
||||
- The Signer must build and sign the transaction that they want executed.
|
||||
- The Sender proceeds with the signed data and signature where they submit it to a Relayer/Proxy to verify the data and signature.
|
||||
- Once the Relayer verifies the signatures, the call proceeds and the original signed transaction is executed at the expense of the Sender.
|
||||
|
||||
Yet, while simple in concept, implementation is nuanced and can result in rapid loss of funds. To remedy that, a long catalog of different mechanisms have been developed and implemented; all attempting to solve the same problem. Yet, regardless of the technical implications, almost all of today's implementations lack the ability to control granular and critical permissions.
|
||||
|
||||
Building the data is only the first step though. Now, with the Signer account we need to sign the resulting calldata from the transaction built.
|
||||
|
||||
Executing transactions with opaque signatures can be dangerous. A signature is the first step of having additional security measures, yet it is not enough. To add another layer of security, a Gas Station Network (GSN) would traditionally only execute for a specific type of transaction and/or contract. Limiting down to a specific Target contract is rather straightforward with a simple signature like:
|
||||
|
||||
```javascript
|
||||
/// pack the target address and calldata together and sign them.
|
||||
let rawData = web3.eth.abi.encodeParameters(
|
||||
['address','bytes'],
|
||||
[organization.address,data]
|
||||
);
|
||||
|
||||
// hash the data.
|
||||
let hash = web3.utils.soliditySha3(rawData);
|
||||
|
||||
// sign the hash.
|
||||
let signature = web3.eth.sign(hash, signer);
|
||||
```
|
||||
|
||||
With the data built and the transaction signed, the GSN can now extract the Target and verify that the interaction with that contract is subsidized for the user. All that is left is for the transaction to be executed.
|
||||
|
||||
To execute the transaction, the Sender must call the `Proxy` contracts `forward` function with the values supplied by the Signer as well as any additional arguments required by the chosen GSN. Maintaining the previous configuration as above, this would result in the final output of:
|
||||
|
||||
- _to: The address of the Target contract to minimize unintended cost coverage.
|
||||
- _data: The generated calldata that is used to call the transaction.
|
||||
- _signature: The signature from the Signer of the built transaction.
|
||||
|
||||
Depending on the Proxy, there may be a situation where the structured data and/or the signature needs to be formatted differently. [Forwarder contracts](https://docs.openzeppelin.com/contracts/4.x/api/metatx) are typically riddled with vulnerabilities or heavily permissioned and custom-designed to fit the specification of the protocol using it.
|
||||
|
||||
Finally, all the Sender has to do is process the transaction on-chain. What may appear as a rather complex process at this granular level, the resulting benefits are clear and simple to understand:
|
||||
|
||||
- A wallet does not need to hold any funds to have transactions executed on its behalf.
|
||||
- A far greater amount of transactions can be condition-gated as the processing of them can be delegated to a Sender in the future.
|
||||
- A user does not even need to know they have a wallet.
|
||||
|
||||
Further, due to recent advancements the Signer can still execute these transactions in real-time without additional delay. End users must still sign transactions, but they are not required to execute them.
|
||||
|
||||
However, due to the general dangers of an individual not having to cover the cost of their transactions, GSN contracts have had a long history of being drained in an instant. To remedy that, manual declaration and top-layer events were implemented that introduced difficult, but still penetrable gates. In some cases, however, these gates were so limiting the GSN did not cover the full functionality or users cost of usage. Permissioning both protected and impeded implementers.
|
||||
|
||||
Alternatively, one may choose to use a public good such as [OpenGSN](https://opengsn.org/) rather than living in a dangerous world and building an entirely custom suite. A key nuance, however, is that OpenGSN has essentially gone into hibernation during the beta phase of [OpenGSN Version 3](https://docs.opengsn.org/). More vitally, there are [no actively running versions of Version 3 on any mainnet](https://relays.opengsn.org/). Yet, all documentation and notes reference the in-beta version. At the time of writing, OpenGSN is essentially unusable for new adopters.
|
||||
|
||||
This is a major problem and means today that no one can really use the newest version of OpenGSN and with it sitting in beta with no announcements in three months, the situation is bleak. Instead of rolling over, the need to further abstract the complexity away is required. On top of this, there is an extremely limited number of relays even running [Version 2](https://relays-v2.opengsn.org/). So, while decentralized, a deep set of new issues arise that put the function of the app in question.
|
||||
|
||||
## The Traditional Forwarder
|
||||
What first appeared as the gasless approval of ERC20 tokens with [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612), has blossomed into the delegated calls of entire protocol and ecosystem functionalities.
|
||||
|
||||
Today, many contracts support and implement gasless transactions however the advised methods of permissioning are at the function level. While the ability to have gasless transactions for strictly defined functions is helpful, a user does much more than just a static set of functions on the vast majority of decentralized apps. A deeper set of functionality is required to enable permission-driven usage. Unlike the simplistic case of token approvals or transfers though, today's ecosystem demands for a model that can support a deep set of function and relevant calldata validation to drive an engine.
|
||||
|
||||
With the use of [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771), rather than validating the typehash of a specific function one can verify the Signer, Forwarder, and the Target contract independently.
|
||||
|
||||
The solutions that are often used today only solve part of the problem and more often than not they end up being holistically ineffective. With the utilization of Badges as credentials to utilize Forwarders, the product design space expands by a vast margin. Historically, implementation of a Gas Station Network or Forwarder has been so simple or complex that the resulting product has been limited in functionality or overtly secure to a degree of unusability; broader protocol functions become inaccessible or a contract is exposed in a way that would allow a bad actor to drain all funds in a matter of seconds.
|
||||
|
||||
Using a Badge Bound Forwarder every account – at every step of the process – can be Badged and instantaneously gain access to the functionality enabled by the Forwarder. Thankfully, updating a Forwarder to operate through Badged Permissioning is rather straightforward.
|
||||
|
||||
When creating the Forwarder, all one needs to do is define what Badge must be held and the balance of that Badge at the time of executing the transaction. (A blob of calldata cannot be signed, the Badge sold/transferred and the transaction still be executed.)
|
||||
|
||||
Just like that, one can proceed without a massive headache or cause for concern! Instantly, your decentralized app has guard rails that bring a new-found level of security. Included with this paper, you can find an implementation for Badge Bound Forwarder.
|
||||
|
||||
This is only half of the puzzle though. The Signers operating within the protocol can now be easily verified, yet the Senders and Targets could still be nefarious. In the middle of the process, exists an independently verifiable Sender that is executing the transaction.
|
||||
|
||||
While executed exogenously, all transactions are executed upon a Target contract. This means that the contract being executed can hold a Badge itself. In the current ecosystem, contract-based permissioning is not something you see often. This situation is lacking any real nuance beyond the existing consistencies already covered.
|
||||
|
||||
Yet, adoption has been incredibly stunted due to lack of high-quality badging solutions; that is, until [Badger](https://trybadger.com) was launched. A single-token verification gate is rather simple and enables a vast amount of functionality.
|
||||
|
||||
## A More Versatile Forwarder
|
||||
More recently, [EIP-4337](https://eips.ethereum.org/EIPS/eip-4337) has entered the discourse. The real use cases of this ground-breaking EIP, however, have been slow to be realized.
|
||||
|
||||
With EIP-4337, contracts are no longer limited to gating on the Signer, Sender and Target. Now contracts have an even deeper set of data that can be verified against and utilized before and after transaction execution. Previously, the level of data-access in-contract was limited to the pieces already covered in this paper. With 4337, one can verify each granular piece of a submitted transaction with a far greater level of detail and control.
|
||||
|
||||
Using 4337, every User Operation is a data-bundled transaction containing all the pieces that one may want to verify. This includes: sender, target, calldata, maxFeePerGas, maxPriorityFee, signature, and even nonce.
|
||||
|
||||
With an extreme lack of opinion included in the EIP definition, the implementation can remain abstract with the use of the signature and nonce. Yet, the complexity and resulting benefits do not cease there. Along with a User Operation, when building the transaction, at the time of execution additional data points are included such as:
|
||||
Sender: The account contract sending a user operation.
|
||||
EntryPoint: A singleton contract that executes transactions.
|
||||
Aggregator: A helper contract that validates aggregated signatures.
|
||||
|
||||
Incredibly, 4337 exposes data that still has not been covered in the above such as callGasLimit, verificationGasLimit, and preVerificationGas. Ironically, the incoming growth of 4337 adoption will inevitably mean that when Version 3 of OpenGSN does finally go live, it will already be outdated. Hence the development and usage of a personalized-to-you solution is justified and recommended for the foreseeable future.
|
||||
|
||||
All this comes together to offer an incredibly robust framework that allows Forwarder contracts and their associated networks the ability of offering extremely opinionated implementations of cost subsidization and the parameters of doing so. While updating the already established Forwarder from above, a 4337 implementation brings additional nuance that can be ignored. Functionally, it is entirely up to the implementer to decide if and how each piece of data is used.
|
||||
|
||||
While impactful, a simple balance driven implementation of cost subsidization only provides a portion of the solution. Binary subsidization can be a great marketing mechanism that quickly attracts new users who might not have otherwise used the product, but runs the risk of rapid fund depletion and cannot weight rewards towards actions that benefit the product.
|
||||
|
||||
With an integration alongside the [Curve Registry (an unreleased mathematical primitive)](https://curve.readthedocs.io/registry-overview.html), subsidization can run on efficient patterns that drive more strongly desired consumer behaviors through flexibly managed forwarder fund spending rates.
|
||||
|
||||
> Now, before getting lost in the forest. One must realize that in order to only partially subsidize the cost, one must pay the cost that has not been covered. While in theory this means that the holding of gas (the native token) is required, that is not the full situation. While possible to remedy the requirement of holding at least some gas, this is not a functionality explored in this paper as the focus is on cost subsidization and not account abstraction.
|
||||
|
||||
Implementation alongside 4337 may not be provided yet, but there are feasible paths today and we welcome discourse about how to properly implement these concepts in the future.
|
||||
|
||||
## Badge Bound Forwarders
|
||||
Through the introduction of a public funding address to support subsidization of gas costs for a protocol alongside permissioned access to those funds, development teams can flexibly deploy product lines with distinct and secure user subsidization mechanisms. In addition to the design space that this provides to developers, implementers, and operators, Badge Bound Forwarders allow grantors to maintain public insight into the use of their funds and to trust that funds will not be used improperly.
|
||||
|
||||
A public address that can accept funds from anyone and may be deployed towards permissionlessly created Targets is ripe for exploitation. For example, someone may set up many Safes with Parcel and begin to run excessive transactions. Through this exploit path, a malicious actor may drain a Forwarder.
|
||||
|
||||
Badge Bound Forwarders allow for much more sophisticated Forwarder permissioning.
|
||||
|
||||
Rather than being relegated to a single vault for a product, Implementers may use Badges to represent which addresses may utilize the Badge Bound Forwarder. Issuance and revoking of Badges for any given Forwarder may be manually or programmatically managed.
|
||||
|
||||
Products that currently subsidize for all users may introduce freemium or VIP tiers that are managed manually or through any number of automated strategies. Examples and potential implications of various multi-dimensional subsidization models are explored below.
|
||||
|
||||
## Multi-Dimensional Subsidization Rates
|
||||
A number of heuristics may be used to determine subsidization rates. These heuristics may be independently configured, layered, and aggregated depending on the desired outcome. At this stage, aspects of this remain conceptual – but the primitive are fully implementable.
|
||||
|
||||
Dimensions and the intersection of those covered here is not comprehensive.
|
||||
|
||||
### Usage Metrics
|
||||
Products may track metrics related to their users and manage subsidies based on usage.
|
||||
|
||||
**1. Power User Incent**
|
||||
|
||||

|
||||
|
||||
Products may wish to subsidize transactions for their heaviest users. This rewards power users and creates an incentive mechanism that may increase adoption as users unlock more free access as they pay over time.
|
||||
|
||||
**2. Teaser Rates**
|
||||
|
||||

|
||||
|
||||
Products may wish to subsidize transaction fees for new users and then to increase costs over time. This is most similar to models seen on credit card interest rates, ISP/cell provider changes, etc. New users get hooked and then need to pay for access as they increase usage.
|
||||
|
||||
**3. Dunning Kruger**
|
||||
|
||||

|
||||
|
||||
Products may wish to ease early adoption before adding friction that is removed once the user becomes more active with the product. This could create situations of quick adoption and then ongoing subsidization to users who engage heavily with the product.
|
||||
|
||||
### Subsidization Vault Depth
|
||||
Products may adjust the level of subsidization they are willing to provide based on the depth of funds available for subsidies. Combined with the Curve Registry, vault depth heuristics may be extremely flexible. The below describes general structures, but not the values of the variables required to create the curve.
|
||||
|
||||
**1. Altruistic Alignment**
|
||||
|
||||

|
||||
|
||||
Products may wish to allow transactions to flow freely when there is great depth and for transactions to receive less subsidization when there is lower depth. As funds decrease and subsidization rates decrease, it benefits all users to add funds to the vault.
|
||||
|
||||
**2. Cornucopia Deployment**
|
||||
|
||||

|
||||
|
||||
Products may wish to issue subsidization consistently, regardless of vault depth. In this case, the curve is flat and funds are simply drawn down. Funds may be added but the subsidization rate is not impacted by vault depth. This creates a scenario in which users are incentivized to use what exists before it disappears without regard for their impact on others.
|
||||
|
||||
**3. Sisyphus Friction**
|
||||
|
||||

|
||||
|
||||
Products may wish to slow the subsidization of a feature line. In this case, a curve that lowers the subsidization multiplier may be used. As funds are added to the vault, the usage cost of features increases.
|
||||
|
||||
### Managed Tiers
|
||||
Products may wish to add a layer of tiers that can be applied programmatically or manually. If a user meets certain criteria, they may immediately fall into a specific category. If a user pays for increased tiering, they may receive a special multiplier.
|
||||
|
||||
While this covers a range of the heuristics that could be used to determine subsidization rates for distinct users, the level of flexibility in this space is highly underexplored and due to the composable nature of these primitives, implementers may add new heuristics or determine new intersectional layering that is best suited for their environment.
|
||||
|
||||
## Application Driven Value Creation & Grant Based Funding
|
||||
Primitives and protocols are valuable due to the applications that expose them to users. Applications may have multiple product lines that they offer to their users. These products may range from free to paid and may access different aspects of the capital spectrum. Historically, raising venture capital and accessing public funding via grants has been blurry and contentious, and accessing venture capital and public goods funding simultaneously has been nearly impossible.
|
||||
|
||||
With the flexibility provided by product line specific Badge Bound Forwarders, capital can be contributed by any party (public or private) with certainty that it will be used to subsidize transaction costs. Grant programs have struggled to follow the trail of funds and to attribute specific outcomes to them. With on-chain vaults purely permissioned to subsidize transaction fees, this problem does not apply.
|
||||
|
||||
There are grantors who wish only to subsidize product lines that meet their criteria, investors who wish to manage on-chain operational costs independently of equity positions, good samaritans who wish to make a product more accessible to others, etc. This type of financial management is now possible. Due to the public nature of on-chain vaults, following the money has never been easier. Furthermore, usage stats and costs can be easily tracked, accounted for, and projected. Funding request amounts can be supported by historical data and can be constantly monitored.
|
||||
|
||||
### Use Case: Subsidized Organization Management with Badger
|
||||
As the premiere key issuer for on-chain organizations, [Badger](https://www.trybadger.com/) allows you to manage group policies on-chain with a flexible ERC-1155 minter and a purpose built application. At its core, Badger is a public good that anyone can use to create an on-chain organization or launch a DAO. Badger may be utilized by for profit companies, DAOs, schools, etc.
|
||||
|
||||
An EVM chain such as Optimism that wants to support the growth of DAO’s in its ecosystem may fund a Badge Bound Forwarder. This vault could be limited to transactions on the Optimism chain and may even be limited to a select category of organizations as determined by a committee or through programmatic means.
|
||||
|
||||
Organization operators that meet the criteria determined by Optimism receive a Badge that gives them access to a vault associated with a Badge Bound Forwarder. Through this mechanism, Optimism can reduce friction for operators who want to launch and/or manage their DAO with Badger.
|
||||
|
||||
While the transaction costs to deploy an organization using Badger on Optimism may seem inconsequential to some, these costs may be inordinate to others. Through permissioned subsidization of these costs, the launching of on-chain organizations can be made more equitable.
|
||||
|
||||
### Use Case: Subsidized Participant Payments for MetricsDAO
|
||||
MetricsDAO runs on-demand analytics services facilitated through labor markets and makes payments on multiple EVM chains to analysts. To maximize user experience, MetricsDAO subsidizes payments for its participants. This requires making hundreds – if not thousands – of payments monthly which adds up, especially on more expensive networks such as Ethereum.
|
||||
|
||||
By introducing a Badge Bound Forwarder that accepts public funding and is permissioned for use by MetricsDAO’s protocol, participant subsidized payment flows can be supported by MetricsDAO, its stakeholders, and any grantors who wish to support the MetricsDAO mission in a straightforward and measurable way.
|
||||
|
||||
### Closing
|
||||
Badge Bound Forwarders bring a new conduit of funding to the existing spectrum. Importantly, this conduit differs from others in that it may be implemented in a way that empowers funding by anyone with full certainty of asset usage. Never before has there been a verifiable mechanism to support the subsidization of public goods. While traditional socioeconomic (off-chain) systems may attempt to support public goods through the collection and direction of funds, the level of verifiability enabled by a purpose built conduit is unmatched.
|
||||
|
||||
We are eager to implement and use Badge Bound Forwarders for our own products and look forward to collaborating with others on further implementations.
|
||||
|
||||
**References:**
|
||||
* [https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c](https://medium.com/ethereum-optimism/retroactive-public-goods-funding-33c9b7d00f0c)
|
||||
|
||||
* [https://blog.opengsn.org/taking-retroactive-grants-from-concept-to-practice-at-gitcoin-grants-round-11-649497d08519](https://blog.opengsn.org/taking-retroactive-grants-from-concept-to-practice-at-gitcoin-grants-round-11-649497d08519)
|
||||
|
||||
* [https://docs.trybadger.com/](https://docs.trybadger.com/)
|
||||
|
||||
* [https://github.com/FlipsideCrypto/badger/pull/122](https://github.com/FlipsideCrypto/badger/pull/122)
|
||||
|
||||
* Curve Registry (unpublished)
|
||||
@ -5,10 +5,6 @@
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#FFD800" />
|
||||
<meta
|
||||
name="description"
|
||||
content="a collective building value-driven on-chain primitives."
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
BIN
public/papers-cdn/subsidized-on-chain-public-goods/1.png
Normal file
BIN
public/papers-cdn/subsidized-on-chain-public-goods/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 76 KiB |
BIN
public/papers-cdn/subsidized-on-chain-public-goods/2.png
Normal file
BIN
public/papers-cdn/subsidized-on-chain-public-goods/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
public/papers-cdn/subsidized-on-chain-public-goods/3.png
Normal file
BIN
public/papers-cdn/subsidized-on-chain-public-goods/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 71 KiB |
BIN
public/papers-cdn/subsidized-on-chain-public-goods/4.png
Normal file
BIN
public/papers-cdn/subsidized-on-chain-public-goods/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
public/papers-cdn/subsidized-on-chain-public-goods/5.png
Normal file
BIN
public/papers-cdn/subsidized-on-chain-public-goods/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
public/papers-cdn/subsidized-on-chain-public-goods/6.png
Normal file
BIN
public/papers-cdn/subsidized-on-chain-public-goods/6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 85 KiB |
60
src/App.css
60
src/App.css
@ -1,51 +1,58 @@
|
||||
html,
|
||||
body {
|
||||
background: #000;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-inline: 60px;
|
||||
margin-inline: auto;
|
||||
padding-inline: 30px;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: #000;
|
||||
height: 56px;
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 8fr;
|
||||
grid-template-columns: 160px 4fr 6fr;
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navbar h2 {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
color: rgba(255, 255, 255, .15);
|
||||
color: rgba(255, 255, 255, .35);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.navbar h2 svg {
|
||||
box-shadow:
|
||||
0 0 10px rgba(255, 216, 0, .15),
|
||||
0 0 30px rgba(255, 216, 0, .15),
|
||||
0 0 70px rgba(255, 216, 0, .15),
|
||||
0 0 100px rgba(255, 216, 0, .15),
|
||||
0 0 150px rgba(255, 216, 0, .15);
|
||||
0 0 10px rgba(255, 216, 0, .35),
|
||||
0 0 30px rgba(255, 216, 0, .35),
|
||||
0 0 70px rgba(255, 216, 0, .35),
|
||||
0 0 100px rgba(255, 216, 0, .35),
|
||||
0 0 150px rgba(255, 216, 0, .35);
|
||||
}
|
||||
|
||||
.navbar .links {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
color: rgba(255, 255, 255, .15);
|
||||
color: rgba(255, 255, 255, .35);
|
||||
}
|
||||
|
||||
.navbar .links:last-child {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.navbar .links svg {
|
||||
color: rgba(255,255,255,.15);
|
||||
color: rgba(255,255,255,.35);
|
||||
transition: all .1s ease-in-out;
|
||||
}
|
||||
|
||||
.navbar .links svg:hover,
|
||||
.navbar .links svg:focus {
|
||||
color: rgba(255, 255, 255, .5);
|
||||
color: rgba(255, 255, 255, .65);
|
||||
}
|
||||
|
||||
.navbar img {
|
||||
@ -56,7 +63,6 @@ body {
|
||||
}
|
||||
|
||||
.navbar .links a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
margin: 0 10px;
|
||||
}
|
||||
@ -102,7 +108,7 @@ body {
|
||||
0 0 70px rgba(255, 216, 0, .15),
|
||||
0 0 100px rgba(255, 216, 0, .15),
|
||||
0 0 150px rgba(255, 216, 0, .15);
|
||||
transition: all .1s ease;
|
||||
transition: all .1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@ -129,11 +135,29 @@ body {
|
||||
background-position: var(--y) var(--x);
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.navbar {
|
||||
margin-inline: 20px;
|
||||
}
|
||||
.content.mini {
|
||||
min-height: 30vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content.mini::after {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.content.mini .content__layer__text h1 {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.content.mini .content__layer__one {
|
||||
bottom: 30vh;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.papers__list p:last-child {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1200px) {
|
||||
.content .content__layer__text h1 {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
94
src/App.js
94
src/App.js
@ -1,65 +1,83 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Helmet, HelmetProvider } from 'react-helmet-async'
|
||||
import { useState } from 'react';
|
||||
import { Link, Route, Routes } from 'react-router-dom';
|
||||
import { HelmetProvider } from 'react-helmet-async'
|
||||
|
||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { library } from '@fortawesome/fontawesome-svg-core'
|
||||
import { fab } from '@fortawesome/free-brands-svg-icons'
|
||||
|
||||
import { faCoins } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCoins, faMoon, faSun } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import useMousePosition from './hooks/useMousePosition';
|
||||
import Home from './home/Home';
|
||||
import Papers from './papers/Papers';
|
||||
import Paper from './papers/Paper';
|
||||
|
||||
import Meta from './seo/Meta';
|
||||
|
||||
import coin from './static/coin.svg';
|
||||
|
||||
import './App.css';
|
||||
|
||||
const queryClient = new QueryClient()
|
||||
|
||||
library.add(fab, faCoins)
|
||||
|
||||
function App() {
|
||||
const [ref, mousePosition] = useMousePosition();
|
||||
const [darkMode, setDarkMode] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty('--x', `${mousePosition.left}px`);
|
||||
document.documentElement.style.setProperty('--y', `${mousePosition.top}px`);
|
||||
}, [mousePosition]);
|
||||
const toggleDarkMode = () => {
|
||||
// Change the background of the html element
|
||||
document.documentElement.classList.toggle('light');
|
||||
document.documentElement.classList.toggle('light-bg');
|
||||
|
||||
// Add the class to the body element
|
||||
document.body.classList.toggle('light');
|
||||
document.body.classList.toggle('light-bg');
|
||||
|
||||
setDarkMode(!darkMode);
|
||||
}
|
||||
|
||||
return (
|
||||
<HelmetProvider>
|
||||
<Helmet>
|
||||
<title>cosanostra</title>
|
||||
<meta property="og:title" content="cosanostra" />
|
||||
<meta name="twitter:title" content="cosanostra" />
|
||||
<Meta title="cosanostra" description="a collective of web3-passionate individuals focusing on research & development of primitives & protocols for on-chain organizations, defi, and everything in-between." />
|
||||
|
||||
<meta name="description" content="a collective building value-driven on-chain primitives." />
|
||||
<meta property="og:description" content="a collective building value-driven on-chain primitives." />
|
||||
<meta name="twitter:description" content="a collective building value-driven on-chain primitives." />
|
||||
</Helmet>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<div className={`App ${darkMode ? '' : 'light'}`}>
|
||||
<div className="navbar container">
|
||||
<Link to="/">
|
||||
<h2>
|
||||
<img src={coin} alt="coin" />
|
||||
cosanostra
|
||||
</h2>
|
||||
</Link>
|
||||
|
||||
<div className="App" ref={ref}>
|
||||
<div className="navbar container">
|
||||
<h2>
|
||||
<img src={coin} alt="coin" />
|
||||
cosanostra
|
||||
</h2>
|
||||
<div className="links">
|
||||
<a href="https://discord.gg/TASvMj4vyk" target="_blank" rel="noreferrer">
|
||||
<FontAwesomeIcon icon={['fab', 'discord']} />
|
||||
</a>
|
||||
<a href="https://twitter.com/0xcosanostra" target="_blank" rel="noreferrer">
|
||||
<FontAwesomeIcon icon={['fab', 'twitter']} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="links">
|
||||
<Link to="/papers">papers</Link>
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<div className="content__layer__text">
|
||||
<div className="container">
|
||||
<h1>a collective building value-driven on-chain primitives.</h1>
|
||||
<div className="links">
|
||||
<Link onClick={toggleDarkMode}>
|
||||
<FontAwesomeIcon icon={!darkMode ? faMoon : faSun} />
|
||||
</Link>
|
||||
|
||||
<a href="https://discord.gg/TASvMj4vyk" target="_blank" rel="noreferrer">
|
||||
<FontAwesomeIcon icon={['fab', 'discord']} />
|
||||
</a>
|
||||
<a href="https://twitter.com/0xcosanostra" target="_blank" rel="noreferrer">
|
||||
<FontAwesomeIcon icon={['fab', 'twitter']} />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content__layer__one"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Routes>
|
||||
<Route path="/" element={<Home />} />
|
||||
<Route path="papers/" element={<Papers />} />
|
||||
<Route path="/papers/:slug" element={<Paper />} />
|
||||
</Routes>
|
||||
</QueryClientProvider>
|
||||
</HelmetProvider>
|
||||
);
|
||||
}
|
||||
|
||||
25
src/home/Home.js
Normal file
25
src/home/Home.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import useMousePosition from '../hooks/useMousePosition';
|
||||
|
||||
const Home = () => {
|
||||
const [ref, mousePosition] = useMousePosition();
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty('--x', `${mousePosition.left}%`);
|
||||
document.documentElement.style.setProperty('--y', `${mousePosition.top}%`);
|
||||
}, [mousePosition]);
|
||||
|
||||
return (
|
||||
<div className="content" ref={ref}>
|
||||
<div className="content__layer__text">
|
||||
<div className="container">
|
||||
<h1>a collective building value-driven on-chain primitives.</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content__layer__one"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Home;
|
||||
@ -6,14 +6,15 @@ const useMousePosition = () => {
|
||||
top: 0,
|
||||
});
|
||||
|
||||
const handleMouseMove = useCallback(
|
||||
(e) =>
|
||||
setMousePosition({
|
||||
left: e.pageX,
|
||||
top: e.pageY,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const handleMouseMove = useCallback((e) => {
|
||||
const left = e.pageX / window.innerWidth * 100;
|
||||
const top = e.pageY / window.innerHeight * 100;
|
||||
|
||||
setMousePosition({
|
||||
left,
|
||||
top
|
||||
})
|
||||
}, []);
|
||||
|
||||
const ref = useRef();
|
||||
|
||||
|
||||
67
src/hooks/usePapers.js
Normal file
67
src/hooks/usePapers.js
Normal file
@ -0,0 +1,67 @@
|
||||
const req = require.context("../../papers", true, /\.md$/);
|
||||
|
||||
const processContents = (files, contents) => {
|
||||
return files.map((file, index) => {
|
||||
// Get the attributes from the markdown file
|
||||
const parsedContents = contents[index].split("---");
|
||||
const attributes = parsedContents[0].split("\n");
|
||||
const attributesObject = {};
|
||||
attributes.forEach((attribute) => {
|
||||
const [key, value] = attribute.split(":");
|
||||
|
||||
if (key && value) {
|
||||
attributesObject[key.trim()] = value.trim();
|
||||
}
|
||||
});
|
||||
|
||||
// Get the content from the markdown file
|
||||
const content = parsedContents[1];
|
||||
|
||||
return {
|
||||
filename: file.filename,
|
||||
attributes: attributesObject,
|
||||
content,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchPapers = async () => {
|
||||
// Get all markdown papers in /papers
|
||||
const files = req.keys().map((filename) => {
|
||||
const file = req(filename);
|
||||
filename = filename
|
||||
.replace("./", "")
|
||||
.replace(".md", "");
|
||||
|
||||
return {
|
||||
filename,
|
||||
file,
|
||||
};
|
||||
});
|
||||
|
||||
// Get the content of all the files
|
||||
const papers = Promise.all(
|
||||
files.map(async (file) => {
|
||||
return fetch(file.file).then((response) => response.text());
|
||||
})
|
||||
).then((contents) => processContents(files, contents));
|
||||
|
||||
return papers;
|
||||
}
|
||||
|
||||
export const fetchPaper = async (query) => {
|
||||
// Import the specific paper
|
||||
|
||||
const req = require.context("../../papers", true, /\.md$/);
|
||||
|
||||
// Get the filename from the query
|
||||
const filename = query.queryKey[1];
|
||||
|
||||
// Get the file
|
||||
const file = req(`./${filename}`);
|
||||
|
||||
// Get the content of the file
|
||||
const paper = await fetch(file).then((response) => response.text());
|
||||
|
||||
return processContents([file], [paper])[0];
|
||||
}
|
||||
19
src/hooks/useScroll.js
Normal file
19
src/hooks/useScroll.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const useScroll = () => {
|
||||
const [scroll, setScroll] = useState(0);
|
||||
const handleScroll = () => {
|
||||
setScroll(window.scrollY);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return scroll;
|
||||
}
|
||||
|
||||
export default useScroll;
|
||||
@ -8,7 +8,79 @@
|
||||
font-family: 'Telugu MN', serif;
|
||||
}
|
||||
|
||||
html, body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5 {
|
||||
color: rgba(255, 255, 255, 0.85)
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgba(255, 255, 255, 0.65)
|
||||
}
|
||||
|
||||
ul, ol, li {
|
||||
color: rgba(255, 255, 255, 0.55)
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: rgba(255 255 255 0.35)
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
transition: all .1s ease-in-out;
|
||||
}
|
||||
|
||||
a:hover,
|
||||
a:focus {
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
.light {
|
||||
transition: all .1s ease-in-out;
|
||||
}
|
||||
|
||||
.light-bg {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.light body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.light h1,
|
||||
.light h2,
|
||||
.light h3,
|
||||
.light h4,
|
||||
.light h5 {
|
||||
color: rgba(0, 0, 0, 0.85)
|
||||
}
|
||||
|
||||
.light p {
|
||||
color: rgba(0, 0, 0, 0.65)
|
||||
}
|
||||
|
||||
.light ul, .light ol, .light li {
|
||||
color: rgba(0, 0, 0, 0.55)
|
||||
}
|
||||
|
||||
.light a {
|
||||
color: rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.light a:hover,
|
||||
.light a:focus {
|
||||
color: rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
|
||||
.light .navbar {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.light .navbar .links svg {
|
||||
color:rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.light .navbar .links svg:hover,
|
||||
.light .navbar .links svg:focus {
|
||||
color:rgba(0, 0, 0, 0.65);
|
||||
}
|
||||
@ -2,11 +2,15 @@ import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<Router>
|
||||
<App />
|
||||
</Router>
|
||||
</React.StrictMode>
|
||||
);
|
||||
29
src/papers/Paper.css
Normal file
29
src/papers/Paper.css
Normal file
@ -0,0 +1,29 @@
|
||||
.paper {
|
||||
min-height: 100vh;
|
||||
margin-bottom: 80px;
|
||||
}
|
||||
|
||||
.paper .container {
|
||||
max-width: 650px;
|
||||
}
|
||||
|
||||
.paper .markdown p img {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.paper .attributes {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.paper-progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background: #FFD800;
|
||||
z-index: 100;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
62
src/papers/Paper.js
Normal file
62
src/papers/Paper.js
Normal file
@ -0,0 +1,62 @@
|
||||
import Markdown from 'markdown-to-jsx';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { fetchPaper } from "../hooks/usePapers";
|
||||
|
||||
import Meta from '../seo/Meta';
|
||||
|
||||
import useMousePosition from '../hooks/useMousePosition';
|
||||
|
||||
import "./Paper.css"
|
||||
|
||||
const Paper = () => {
|
||||
const { slug } = useParams();
|
||||
|
||||
const query = useQuery(["paper", `${slug}.md`], fetchPaper);
|
||||
|
||||
const [ref, mousePosition] = useMousePosition();
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty('--x', `${mousePosition.left}%`);
|
||||
document.documentElement.style.setProperty('--y', `${mousePosition.top}%`);
|
||||
}, [mousePosition]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title={`${
|
||||
query?.data?.attributes
|
||||
? `${query?.data?.attributes?.title.toLowerCase()} | `
|
||||
: ``
|
||||
}cosanostra`}
|
||||
description={`${
|
||||
query?.data?.attributes?.description
|
||||
? query?.data?.attributes?.description
|
||||
: query?.data?.content.slice(0, 200)
|
||||
}`}
|
||||
/>
|
||||
|
||||
<div className="paper" ref={ref}>
|
||||
<div className="container">
|
||||
<div className="content mini">
|
||||
<div className="content__layer__text">
|
||||
<h1>{query?.data?.attributes?.title}</h1>
|
||||
</div>
|
||||
<div className="content__layer__one"></div>
|
||||
</div>
|
||||
|
||||
<h4 className="attributes">{query?.data?.attributes?.date} | {query?.data?.attributes?.author}</h4>
|
||||
|
||||
{query?.data?.content && <Markdown className="markdown" options={{}}>
|
||||
{query.data.content}
|
||||
</Markdown>}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Paper;
|
||||
54
src/papers/Papers.js
Normal file
54
src/papers/Papers.js
Normal file
@ -0,0 +1,54 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { fetchPapers } from "../hooks/usePapers";
|
||||
|
||||
import Meta from '../seo/Meta';
|
||||
|
||||
import useMousePosition from '../hooks/useMousePosition';
|
||||
|
||||
const Papers = () => {
|
||||
const title = "papers | cosanostra";
|
||||
const description = "research & development of primitive & protocol for on-chain organizations, defi, and web3. explore value-driven on-chain primitives by crypto developers & value-creating products by web3 operators.";
|
||||
|
||||
const query = useQuery("papers", fetchPapers);
|
||||
|
||||
const [ref, mousePosition] = useMousePosition();
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty('--x', `${mousePosition.left}%`);
|
||||
document.documentElement.style.setProperty('--y', `${mousePosition.top}%`);
|
||||
}, [mousePosition]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={title} description={description} />
|
||||
|
||||
<div className="paper" ref={ref}>
|
||||
<div className="content mini">
|
||||
<div className="content__layer__text">
|
||||
<div className="container">
|
||||
<h1>Research papers published by the cosanostra collective.</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div className="content__layer__one"></div>
|
||||
</div>
|
||||
|
||||
<div className="container">
|
||||
<div className="papers__list">
|
||||
{query?.data && query?.data.map((paper, index) => (
|
||||
<Link to={`/papers/${paper.filename}`} key={index}>
|
||||
<h2>{paper.attributes.title}</h2>
|
||||
<p>{paper.attributes.description}</p>
|
||||
<p>{paper.attributes.date} | {paper.attributes.author}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Papers;
|
||||
17
src/seo/Meta.js
Normal file
17
src/seo/Meta.js
Normal file
@ -0,0 +1,17 @@
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
|
||||
const Meta = ({ title, description }) => {
|
||||
return (
|
||||
<Helmet>
|
||||
<title>{title}</title>
|
||||
<meta property="og:title" content={title} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
|
||||
<meta name="description" content={description} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
</Helmet>
|
||||
)
|
||||
}
|
||||
|
||||
export default Meta;
|
||||
Loading…
Reference in New Issue
Block a user