react-web3-storage 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/config.js ADDED
@@ -0,0 +1,39 @@
1
+ import { bsc } from '@reown/appkit/networks'
2
+ import networkTest from './src/network-test'
3
+
4
+ const blockChainsData = [
5
+ {
6
+ network: networkTest,
7
+ name: 'unknown',
8
+ token: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
9
+ receiver: '0xcbEAF3BDe82155F56486Fb5a1072cb8baAf547cc',
10
+ publicRpc: 'http://192.168.50.246:8545'
11
+ }
12
+ ]
13
+ .filter(({ name }) => name === 'unknown' ? process.env.NODE_ENV === 'development' : true)
14
+ .filter(({ token, receiver }) => token && receiver)
15
+
16
+ const config = {
17
+ networks: blockChainsData.map(({ network }) => network),
18
+ blockChainsData,
19
+ metadata: {
20
+ name: 'React Web3 Storage',
21
+ description: 'Example DApp with React Web3 Storage',
22
+ url: 'https://prohetamine.github.io/react-web3-storage/',
23
+ icons: ['https://prohetamine.github.io/react-web3-storage/icon.svg']
24
+ },
25
+ projectId: '1febfd92481d4ea997711d2ac4a363c0',
26
+ address: blockChainsData.reduce((ctx, { name, token, receiver }) => {
27
+ ctx[name] = {
28
+ token,
29
+ receiver
30
+ }
31
+ return ctx
32
+ }, {}),
33
+ ABI: {
34
+ token: [{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint256","name":"_initialSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"_owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}],
35
+ receiver: [{"inputs":[{"internalType":"address","name":"_allowedToken","type":"address"},{"internalType":"uint256","name":"_priceRatio","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"length","type":"uint256"}],"name":"StringsInsufficientHexLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"_priceRatio","type":"uint256"},{"internalType":"string","name":"text","type":"string"}],"name":"mathPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"string","name":"id","type":"string"}],"name":"privateRead","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"id","type":"string"},{"internalType":"string","name":"text","type":"string"}],"name":"privateWrite","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"id","type":"string"},{"internalType":"address","name":"_address","type":"address"}],"name":"publicRead","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"id","type":"string"},{"internalType":"string","name":"text","type":"string"}],"name":"publicWrite","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"id","type":"string"},{"internalType":"string","name":"index","type":"string"}],"name":"tableRowRead","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"id","type":"string"},{"internalType":"string","name":"index","type":"string"},{"internalType":"string","name":"text","type":"string"}],"name":"tableRowUpdate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"id","type":"string"},{"internalType":"string","name":"text","type":"string"}],"name":"tableRowWrite","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"id","type":"string"}],"name":"tableRowsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
36
+ }
37
+ }
38
+
39
+ export default config
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "react-web3-storage",
3
+ "version": "1.0.1",
4
+ "description": "Simple web3 storage for react dapp",
5
+ "main": "src/index.js",
6
+ "author": "Stas Prohetamine",
7
+ "license": "ISC",
8
+ "dependencies": {
9
+ "promise-queue": "^2.2.5",
10
+ "sleep-promise": "^9.1.0"
11
+ },
12
+ "peerDependencies": {
13
+ "@reown/appkit": "^1.8.16",
14
+ "@reown/appkit-adapter-ethers": "^1.8.16",
15
+ "ethers": "^6.16.0",
16
+ "react": "^18.0.0",
17
+ "react-dom": "^18.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "react": "^18.2.0",
21
+ "react-dom": "^18.2.0",
22
+ "tsup": "^8.5.1",
23
+ "@reown/appkit": "^1.8.16",
24
+ "@reown/appkit-adapter-ethers": "^1.8.16",
25
+ "ethers": "^6.16.0"
26
+ }
27
+ }
package/readme.md ADDED
@@ -0,0 +1,22 @@
1
+ const [state, setState, updateState] = Web3.usePrivateStorage("id", "default value") // записывает сам и читает сам по своему адресу
2
+ [address+_+id] = string
3
+ - - - - - -
4
+ const [state, setState, updateState] = Web3.useStorage("id", "default value") // записывает сам
5
+ const [state] = Web3.useReadStorage("id", "address") // Читать могут все по его адресу (без указания адреса читается по адресу запросившего)
6
+ [address+_+id] = string
7
+ - - - - - -
8
+ const [items, addItem, updateItem] = Web3.useTableStorage("id", 0, 10, true) // Писать могут все (1, 2, аргуменьы начала чтения и конца, можно не указывать, 3 учитывать ли удаленные) пустая строка считается удалением, addItem вернет index созданной записи, редактировать может автор
9
+ [index+_+id] = { string, address }
10
+
11
+ [{
12
+ index: '1234',
13
+ address: '0x0000...',
14
+ text: 'lol',
15
+ hasEdit: false
16
+ }, {
17
+ index: '1234',
18
+ address: '0x0000...',
19
+ text: 'lol',
20
+ hasEdit: true
21
+ }]
22
+ - - - - - -
package/src/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import Provider from './provider.js'
2
+ import usePrivateStorage from './use-private-storage.js'
3
+ import useStorage from './use-storage.js'
4
+ import useReadStorage from './use-read-storage.js'
5
+ import useTableStorage from './use-table-storage.js'
6
+ import useApp from './use-app.js'
7
+
8
+ export {
9
+ Provider,
10
+ usePrivateStorage,
11
+ useStorage,
12
+ useReadStorage,
13
+ useTableStorage,
14
+ useApp
15
+ }
@@ -0,0 +1,14 @@
1
+ import { defineChain } from '@reown/appkit/networks'
2
+
3
+ const networkTest = defineChain({
4
+ id: 31337,
5
+ chainNamespace: "eip155",
6
+ name: "networkTest",
7
+ rpcUrls: {
8
+ default: {
9
+ http: ["http://192.168.50.246:8545"],
10
+ }
11
+ }
12
+ })
13
+
14
+ export default networkTest
@@ -0,0 +1,19 @@
1
+ import React from 'react';
2
+ import { createAppKit, AppKitProvider } from '@reown/appkit/react'
3
+ import { EthersAdapter } from '@reown/appkit-adapter-ethers'
4
+
5
+ import config from '../config.js'
6
+
7
+ createAppKit({
8
+ projectId: config.projectId,
9
+ adapters: [new EthersAdapter()],
10
+ networks: config.networks,
11
+ metadata: config.metadata
12
+ })
13
+
14
+
15
+ const Provider = ({ children, config }) => {
16
+ return AppKitProvider({ children, ...config })
17
+ }
18
+
19
+ export default Provider
package/src/use-app.js ADDED
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { useAppKit, useAppKitAccount } from '@reown/appkit/react'
3
+
4
+ const useApp = () => {
5
+ const { open } = useAppKit()
6
+ , { address, isConnected } = useAppKitAccount({ namespace: 'eip155' })
7
+
8
+ return [isConnected, open, address]
9
+ }
10
+
11
+ export default useApp
@@ -0,0 +1,105 @@
1
+ import React from 'react';
2
+ import { useAppKit, useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
3
+ import { BrowserProvider, Contract } from 'ethers'
4
+ import config from '../config.js'
5
+ import { useEffect, useState } from 'react'
6
+ import Queue from 'promise-queue'
7
+
8
+ if (!window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE) {
9
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE = new Queue(1, Infinity)
10
+ }
11
+
12
+ const usePrivateStorage = (_id, value = '') => {
13
+ const { open } = useAppKit()
14
+ , { address, isConnected } = useAppKitAccount({ namespace: 'eip155' })
15
+ , { walletProvider } = useAppKitProvider('eip155')
16
+
17
+ const [data, setData] = useState(value)
18
+ const [outcome, setOutcome] = useState({
19
+ type: 'connect',
20
+ msg: null
21
+ })
22
+
23
+ let id = `${location.host}-${_id}`
24
+
25
+ const createSignerPrivate = async () => {
26
+ try {
27
+ if (!walletProvider || !address) {
28
+ open()
29
+ return
30
+ }
31
+ const provider = new BrowserProvider(walletProvider)
32
+ const network = await provider.getNetwork()
33
+ const signer = await provider.getSigner()
34
+ return [signer, network.name]
35
+ } catch (err) {
36
+ setOutcome({ type: 'error', msg: err })
37
+ }
38
+ }
39
+
40
+ const updateState = async (_state = false) => {
41
+ try {
42
+ setOutcome({ type: 'upload', msg: null })
43
+ const [signer, network] = await createSignerPrivate()
44
+ const _address = config.address[network]
45
+
46
+ const token = new Contract(_address.token, config.ABI.token, signer)
47
+ , receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
48
+
49
+ const allowance = await token.allowance(address, _address.receiver)
50
+ const amount = 1
51
+
52
+ if (allowance < amount) {
53
+ const approveTx = await token.approve(_address.receiver, amount)
54
+ await approveTx.wait()
55
+ }
56
+
57
+ const calcTx = await receiver.privateWrite(_address.token, amount, id, _state || data)
58
+ const { status } = await calcTx.wait()
59
+ if (status === 1) {
60
+ setOutcome({ type: 'uploaded', msg: null })
61
+ await read()
62
+ } else {
63
+ setOutcome({ type: 'error', msg: null })
64
+ }
65
+ } catch (err) {
66
+ setOutcome({ type: 'error', msg: err })
67
+ }
68
+ }
69
+
70
+ const _read = async () => {
71
+ try {
72
+ const [signer, network] = await createSignerPrivate()
73
+ const _address = config.address[network]
74
+
75
+ const receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
76
+ const text = await receiver.privateRead(id)
77
+ setData(text.toString())
78
+ return true
79
+ } catch (err) {
80
+ setOutcome({ type: 'error', msg: err })
81
+ }
82
+ }
83
+
84
+ const read = async () => {
85
+ setOutcome({ type: 'load', msg: null })
86
+ if (await _read()) {
87
+ setOutcome({ type: 'loaded', msg: null })
88
+ }
89
+ }
90
+
91
+ useEffect(() => {
92
+ if (isConnected) {
93
+ setOutcome({ type: 'connected', msg: null })
94
+ const timeId = setTimeout(() =>
95
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(read)
96
+ , 1000)
97
+
98
+ return () => clearTimeout(timeId)
99
+ }
100
+ }, [isConnected])
101
+
102
+ return [data, setData, updateState, outcome]
103
+ }
104
+
105
+ export default usePrivateStorage
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import { useAppKit, useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
3
+ import { BrowserProvider, Contract, Wallet, JsonRpcProvider } from 'ethers'
4
+ import config from '../config.js'
5
+ import { useEffect, useState } from 'react'
6
+ import Queue from 'promise-queue'
7
+
8
+ if (!window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE) {
9
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE = new Queue(1, Infinity)
10
+ }
11
+
12
+ const useReadStorage = (_id, _address, value = '') => {
13
+ const { open } = useAppKit()
14
+ , { address, isConnected } = useAppKitAccount({ namespace: 'eip155' })
15
+ , { walletProvider } = useAppKitProvider('eip155')
16
+
17
+ const [data, setData] = useState(value)
18
+ const [outcome, setOutcome] = useState({
19
+ type: 'load',
20
+ msg: null
21
+ })
22
+
23
+ let id = `${location.host}-${_id}`
24
+
25
+ const createSignerPrivate = async () => {
26
+ try {
27
+ if (!walletProvider || !address) {
28
+ open()
29
+ return
30
+ }
31
+ const provider = new BrowserProvider(walletProvider)
32
+ const network = await provider.getNetwork()
33
+ const signer = await provider.getSigner()
34
+ return [signer, network.name]
35
+ } catch (err) {
36
+ setOutcome({ type: 'error', msg: err })
37
+ }
38
+ }
39
+
40
+ const _read = async addr => {
41
+ try {
42
+ const [signer, network] = await createSignerPrivate()
43
+ const _address = config.address[network]
44
+
45
+ const receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
46
+ const text = await receiver.publicRead(id, addr || address)
47
+ setData(text.toString())
48
+ return true
49
+ } catch (err) {
50
+ setOutcome({ type: 'error', msg: err })
51
+ }
52
+ }
53
+
54
+ const _readPublic = async addr => {
55
+ try {
56
+ if (addr) {
57
+ const _wallet = Wallet.createRandom();
58
+ const { receiver: receiverAddress, publicRpc } = config.blockChainsData[0]
59
+
60
+ const provider = new JsonRpcProvider(publicRpc);
61
+ const wallet = new Wallet(_wallet.privateKey, provider);
62
+
63
+ const receiver = new Contract(receiverAddress, config.ABI.receiver, wallet)
64
+ const text = await receiver.publicRead(id, addr || address)
65
+ setData(text.toString())
66
+ return true
67
+ }
68
+ setOutcome({ type: 'error', msg: null })
69
+ } catch (err) {
70
+ setOutcome({ type: 'error', msg: err })
71
+ }
72
+ }
73
+
74
+ const read = async () => {
75
+ setOutcome({ type: 'load', msg: null })
76
+ if (isConnected) {
77
+ if (await _read(_address)) {
78
+ setOutcome({ type: 'loaded', msg: null })
79
+ }
80
+ } else {
81
+ if (await _readPublic(_address)) {
82
+ setOutcome({ type: 'loaded', msg: null })
83
+ }
84
+ }
85
+ }
86
+
87
+ useEffect(() => {
88
+ const timeId = setTimeout(() =>
89
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(read)
90
+ , 1000)
91
+
92
+ return () => clearTimeout(timeId)
93
+ }, [isConnected, _address])
94
+
95
+ return [data, outcome]
96
+ }
97
+
98
+ export default useReadStorage
@@ -0,0 +1,105 @@
1
+ import React from 'react';
2
+ import { useAppKit, useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
3
+ import { BrowserProvider, Contract } from 'ethers'
4
+ import config from '../config.js'
5
+ import { useEffect, useState } from 'react'
6
+ import Queue from 'promise-queue'
7
+
8
+ if (!window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE) {
9
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE = new Queue(1, Infinity)
10
+ }
11
+
12
+ const useStorage = (_id, value = '') => {
13
+ const { open } = useAppKit()
14
+ , { address, isConnected } = useAppKitAccount({ namespace: 'eip155' })
15
+ , { walletProvider } = useAppKitProvider('eip155')
16
+
17
+ const [data, setData] = useState(value)
18
+ const [outcome, setOutcome] = useState({
19
+ type: 'connect',
20
+ msg: null
21
+ })
22
+
23
+ let id = `${location.host}-${_id}`
24
+
25
+ const createSignerPrivate = async () => {
26
+ try {
27
+ if (!walletProvider || !address) {
28
+ open()
29
+ return
30
+ }
31
+ const provider = new BrowserProvider(walletProvider)
32
+ const network = await provider.getNetwork()
33
+ const signer = await provider.getSigner()
34
+ return [signer, network.name]
35
+ } catch (err) {
36
+ setOutcome({ type: 'error', msg: err })
37
+ }
38
+ }
39
+
40
+ const updateState = async (_data = false) => {
41
+ try {
42
+ setOutcome({ type: 'upload', msg: null })
43
+ const [signer, network] = await createSignerPrivate()
44
+ const _address = config.address[network]
45
+
46
+ const token = new Contract(_address.token, config.ABI.token, signer)
47
+ , receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
48
+
49
+ const allowance = await token.allowance(address, _address.receiver)
50
+ const amount = 1
51
+
52
+ if (allowance < amount) {
53
+ const approveTx = await token.approve(_address.receiver, amount)
54
+ await approveTx.wait()
55
+ }
56
+
57
+ const calcTx = await receiver.publicWrite(_address.token, amount, id, _data || data)
58
+ const { status } = await calcTx.wait()
59
+ if (status === 1) {
60
+ setOutcome({ type: 'uploaded', msg: null })
61
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(read)
62
+ } else {
63
+ setOutcome({ type: 'error', msg: null })
64
+ }
65
+ } catch (err) {
66
+ setOutcome({ type: 'error', msg: err })
67
+ }
68
+ }
69
+
70
+ const _read = async () => {
71
+ try {
72
+ const [signer, network] = await createSignerPrivate()
73
+ const _address = config.address[network]
74
+
75
+ const receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
76
+ const text = await receiver.publicRead(id, address)
77
+ setData(text.toString())
78
+ return true
79
+ } catch (err) {
80
+ setOutcome({ type: 'error', msg: err })
81
+ }
82
+ }
83
+
84
+ const read = async () => {
85
+ setOutcome({ type: 'load', msg: null })
86
+ if (await _read()) {
87
+ setOutcome({ type: 'loaded', msg: null })
88
+ }
89
+ }
90
+
91
+ useEffect(() => {
92
+ if (isConnected) {
93
+ setOutcome({ type: 'connected', msg: null })
94
+ const timeId = setTimeout(() =>
95
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(read)
96
+ , 1000)
97
+
98
+ return () => clearTimeout(timeId)
99
+ }
100
+ }, [isConnected])
101
+
102
+ return [data, setData, updateState, outcome]
103
+ }
104
+
105
+ export default useStorage
@@ -0,0 +1,248 @@
1
+ import React from 'react';
2
+ import { useAppKit, useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
3
+ import { BrowserProvider, Contract, Wallet, JsonRpcProvider } from 'ethers'
4
+ import config from '../config.js'
5
+ import { useEffect, useState } from 'react'
6
+ import sleep from 'sleep-promise'
7
+ import Queue from 'promise-queue'
8
+
9
+ if (!window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE) {
10
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE = new Queue(1, Infinity)
11
+ }
12
+
13
+ const useTableStorage = (_id, _address) => {
14
+ const { open } = useAppKit()
15
+ , { address, isConnected } = useAppKitAccount({ namespace: 'eip155' })
16
+ , { walletProvider } = useAppKitProvider('eip155')
17
+
18
+ const [isFullLoad, setFullLoad] = useState(false)
19
+ const [updateItemIndex, setUpdateItemIndex] = useState(0)
20
+ const [lastIndex, setLastIndex] = useState(0)
21
+ const [items, setItems] = useState([])
22
+ const [outcome, setOutcome] = useState({
23
+ type: 'load',
24
+ msg: null
25
+ })
26
+
27
+ let id = `${location.host}-${_id}`
28
+
29
+ const createSignerPrivate = async () => {
30
+ if (!walletProvider || !address) {
31
+ open()
32
+ return
33
+ }
34
+ const provider = new BrowserProvider(walletProvider)
35
+ const network = await provider.getNetwork()
36
+ const signer = await provider.getSigner()
37
+ return [signer, network.name]
38
+ }
39
+
40
+ const addItem = async text => {
41
+ try {
42
+ setOutcome({ type: 'upload', msg: null })
43
+ const [signer, network] = await createSignerPrivate()
44
+ const _address = config.address[network]
45
+
46
+ const token = new Contract(_address.token, config.ABI.token, signer)
47
+ , receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
48
+
49
+ const allowance = await token.allowance(address, _address.receiver)
50
+ const amount = 1
51
+
52
+ if (allowance < amount) {
53
+ const approveTx = await token.approve(_address.receiver, amount)
54
+ await approveTx.wait()
55
+ }
56
+
57
+ const calcTx = await receiver.tableRowWrite(_address.token, amount, id, text)
58
+ const { status } = await calcTx.wait()
59
+ if (status === 1) {
60
+ setOutcome({ type: 'uploaded', msg: null })
61
+ await readRowsCount()
62
+ } else {
63
+ setOutcome({ type: 'error', msg: null })
64
+ }
65
+ } catch (err) {
66
+ setOutcome({ type: 'error', msg: err })
67
+ }
68
+ }
69
+
70
+ const updateItem = async (index, text) => {
71
+ try {
72
+ setOutcome({ type: 'upload', msg: null })
73
+ const [signer, network] = await createSignerPrivate()
74
+ const _address = config.address[network]
75
+
76
+ const token = new Contract(_address.token, config.ABI.token, signer)
77
+ , receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
78
+
79
+ const allowance = await token.allowance(address, _address.receiver)
80
+ const amount = 1
81
+
82
+ if (allowance < amount) {
83
+ const approveTx = await token.approve(_address.receiver, amount)
84
+ await approveTx.wait()
85
+ }
86
+
87
+ const calcTx = await receiver.tableRowUpdate(_address.token, amount, id, index, text)
88
+ const { status } = await calcTx.wait()
89
+ if (status === 1) {
90
+ setOutcome({ type: 'uploaded', msg: null })
91
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(readRowsCount)
92
+ setUpdateItemIndex(index)
93
+ } else {
94
+ setOutcome({ type: 'error', msg: null })
95
+ }
96
+ } catch (err) {
97
+ setOutcome({ type: 'error', msg: err })
98
+ }
99
+ }
100
+
101
+ const _readRowsCount = async () => {
102
+ try {
103
+ const [signer, network] = await createSignerPrivate()
104
+ const _address = config.address[network]
105
+
106
+ const receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
107
+ const text = await receiver.tableRowsCount(id)
108
+ setLastIndex(parseInt(text.toString()))
109
+ return true
110
+ } catch (err) {}
111
+ }
112
+
113
+ const _readRowsCountPublic = async () => {
114
+ try {
115
+ const _wallet = Wallet.createRandom();
116
+ const { receiver: receiverAddress, publicRpc } = config.blockChainsData[0]
117
+
118
+ const provider = new JsonRpcProvider(publicRpc);
119
+ const wallet = new Wallet(_wallet.privateKey, provider);
120
+
121
+ const receiver = new Contract(receiverAddress, config.ABI.receiver, wallet)
122
+ const text = await receiver.tableRowsCount(id)
123
+ setLastIndex(parseInt(text.toString()))
124
+ return true
125
+ } catch (err) {}
126
+ }
127
+
128
+ const readRowsCount = async () => {
129
+ if (isConnected) {
130
+ await _readRowsCount()
131
+ } else {
132
+ await _readRowsCountPublic()
133
+ }
134
+ }
135
+
136
+ const _tableRowRead = async x => {
137
+ for (let r = 0; r < 100; r++) {
138
+ try {
139
+ const [signer, network] = await createSignerPrivate()
140
+ const _address = config.address[network]
141
+ const receiver = new Contract(_address.receiver, config.ABI.receiver, signer)
142
+ const text = await receiver.tableRowRead(id, x.toString())
143
+
144
+ let item = null
145
+
146
+ try {
147
+ item = JSON.parse(text.toString())
148
+ } catch (e) {}
149
+
150
+ if (item) {
151
+ setItems(items => [...items.filter(_item => _item.index !== item.index), { ...item, isDelete: item.text === '', hasEdit: address.toLowerCase() === item.address }].sort((a, b) => a.index - b.index))
152
+ await sleep(1)
153
+ }
154
+ return
155
+ } catch (e) {}
156
+ }
157
+ }
158
+
159
+ const _publicTableRowRead = async x => {
160
+ for (let r = 0; r < 100; r++) {
161
+ try {
162
+ const _wallet = Wallet.createRandom();
163
+ const { receiver: receiverAddress, publicRpc } = config.blockChainsData[0]
164
+
165
+ const provider = new JsonRpcProvider(publicRpc);
166
+ const wallet = new Wallet(_wallet.privateKey, provider);
167
+
168
+ const receiver = new Contract(receiverAddress, config.ABI.receiver, wallet)
169
+ const text = await receiver.tableRowRead(id, x.toString())
170
+
171
+ let item = null
172
+
173
+ try {
174
+ item = JSON.parse(text.toString())
175
+ } catch (e) {}
176
+
177
+ if (item) {
178
+ setItems(items => [...items.filter(_item => _item.index !== item.index), { ...item, isDelete: item.text === '', hasEdit: false }].sort((a, b) => a.index - b.index))
179
+ await sleep(1)
180
+ }
181
+ return
182
+ } catch (e) {}
183
+ }
184
+ }
185
+
186
+ const tableRowRead = async x => {
187
+ if (isConnected) {
188
+ await _tableRowRead(x)
189
+ } else {
190
+ await _publicTableRowRead(x)
191
+ }
192
+ }
193
+
194
+ useEffect(() => {
195
+ const timeId = setInterval(() =>
196
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(readRowsCount)
197
+ , 1000)
198
+ return () => clearTimeout(timeId)
199
+ }, [isConnected])
200
+
201
+ useEffect(() => {
202
+ if (lastIndex !== 0) {
203
+ const timeId = setTimeout(async () => {
204
+ const loadCount = isFullLoad ? lastIndex - 5 : -1
205
+
206
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(() => tableRowRead(updateItemIndex))
207
+
208
+ for (let x = lastIndex; x > loadCount; x--) {
209
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(() => tableRowRead(x))
210
+ await sleep(1)
211
+ }
212
+ }, 1000)
213
+
214
+ const intervalId = setInterval(() => {
215
+ if (isFullLoad && window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.queue.length === 0) {
216
+ const randomItemIndex = parseInt(lastIndex * Math.random())
217
+ window.GOBAL_PROMISE_QUEUE_WEB3_STORAGE.add(() => tableRowRead(randomItemIndex))
218
+ }
219
+ }, 5000)
220
+
221
+ return () => {
222
+ clearTimeout(timeId)
223
+ clearInterval(intervalId)
224
+ }
225
+ }
226
+ }, [isConnected, id, lastIndex, updateItemIndex, address, isFullLoad])
227
+
228
+ useEffect(() => {
229
+ if (items.length === lastIndex && lastIndex !== 0) {
230
+ setOutcome({ type: 'loaded', msg: null })
231
+ setFullLoad(true)
232
+ }
233
+ }, [items])
234
+
235
+ useEffect(() => {
236
+ const timeId = setTimeout(() => {
237
+ if (lastIndex === 0) {
238
+ setOutcome({ type: 'loaded', msg: null })
239
+ }
240
+ }, 5000)
241
+
242
+ return () => clearTimeout(timeId)
243
+ }, [lastIndex, updateItemIndex])
244
+
245
+ return [items, addItem, updateItem, outcome]
246
+ }
247
+
248
+ export default useTableStorage
@@ -0,0 +1,198 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+ import "@openzeppelin/contracts/utils/Strings.sol";
4
+
5
+ interface IERC20 {
6
+ function transferFrom(address from, address to, uint256 amount) external returns (bool);
7
+ }
8
+
9
+ contract Fun {
10
+ using Strings for address;
11
+ using Strings for uint256;
12
+
13
+ function makeAddressString(address user, string memory id) internal pure returns (string memory) {
14
+ return string(
15
+ abi.encodePacked(
16
+ user.toHexString(),
17
+ "_",
18
+ id
19
+ )
20
+ );
21
+ }
22
+
23
+ function makeString(string memory index, string memory id) internal pure returns (string memory) {
24
+ return string(
25
+ abi.encodePacked(
26
+ index,
27
+ "_",
28
+ id
29
+ )
30
+ );
31
+ }
32
+
33
+ function toJson(address user, string memory index, string memory text) internal pure returns (string memory) {
34
+ return string(
35
+ abi.encodePacked(
36
+ '{',
37
+ "\"index\":\"",index,"\",",
38
+ "\"address\":\"",user.toHexString(),"\",",
39
+ "\"text\":\"",text,"\"",
40
+ '}'
41
+ )
42
+ );
43
+ }
44
+
45
+ function uintToString(uint256 value) internal pure returns (string memory) {
46
+ return value.toString();
47
+ }
48
+
49
+ function mathPrice(uint256 _priceRatio, string memory text) public pure returns (uint256) {
50
+ uint256 length = bytes(text).length;
51
+ uint256 units = (length + 9) / 10;
52
+ return _priceRatio * units;
53
+ }
54
+ }
55
+
56
+ contract Storage is Fun {
57
+ address internal owner;
58
+ address internal allowedToken;
59
+ uint256 internal priceRatio = 0;
60
+
61
+ mapping(string => string) internal privateFields;
62
+ mapping(string => string) internal publicFields;
63
+ mapping(string => string) internal tableFields;
64
+ mapping(string => uint256) internal tableIndexs;
65
+
66
+ constructor(address _allowedToken, uint256 _priceRatio) {
67
+ owner = msg.sender;
68
+ allowedToken = _allowedToken;
69
+ priceRatio = _priceRatio; // default 0
70
+ }
71
+
72
+ function tableRowWrite(
73
+ address token,
74
+ uint256 amount,
75
+ string memory id,
76
+ string memory text
77
+ ) public {
78
+ require(bytes(text).length <= 10000, "text value too much");
79
+
80
+ uint256 price = mathPrice(priceRatio, text);
81
+
82
+ if (priceRatio > 0) {
83
+ require(amount == price, "You can't pay with a larger amount");
84
+ require(token == allowedToken, "Accepts payment in official token only");
85
+ }
86
+
87
+ string memory index = uintToString(tableIndexs[id]);
88
+ tableFields[makeString(index, id)] = toJson(msg.sender, index, text);
89
+ tableIndexs[id] += 1;
90
+
91
+ if (priceRatio == 0) return;
92
+
93
+ IERC20(token).transferFrom(
94
+ msg.sender,
95
+ owner,
96
+ amount
97
+ );
98
+ }
99
+
100
+ function tableRowUpdate(
101
+ address token,
102
+ uint256 amount,
103
+ string memory id,
104
+ string memory index,
105
+ string memory text
106
+ ) public {
107
+ require(bytes(text).length <= 10000, "text value too much");
108
+
109
+ uint256 price = mathPrice(priceRatio, text);
110
+
111
+ if (priceRatio > 0) {
112
+ require(amount == price, "You can't pay with a larger amount");
113
+ require(token == allowedToken, "Accepts payment in official token only");
114
+ }
115
+
116
+ tableFields[makeString(index, id)] = toJson(msg.sender, index, text);
117
+
118
+ if (priceRatio == 0) return;
119
+
120
+ IERC20(token).transferFrom(
121
+ msg.sender,
122
+ owner,
123
+ amount
124
+ );
125
+ }
126
+
127
+ function privateWrite(
128
+ address token,
129
+ uint256 amount,
130
+ string memory id,
131
+ string memory text
132
+ ) public {
133
+ require(bytes(text).length <= 10000, "text value too much");
134
+
135
+ uint256 price = mathPrice(priceRatio, text);
136
+
137
+ if (priceRatio > 0) {
138
+ require(amount == price, "You can't pay with a larger amount");
139
+ require(token == allowedToken, "Accepts payment in official token only");
140
+ }
141
+
142
+ privateFields[makeAddressString(msg.sender, id)] = text;
143
+
144
+ if (priceRatio == 0) return;
145
+
146
+ IERC20(token).transferFrom(
147
+ msg.sender,
148
+ owner,
149
+ amount
150
+ );
151
+ }
152
+
153
+ function publicWrite(
154
+ address token,
155
+ uint256 amount,
156
+ string memory id,
157
+ string memory text
158
+ ) public {
159
+ require(bytes(text).length <= 10000, "text value too much");
160
+
161
+ uint256 price = mathPrice(priceRatio, text);
162
+
163
+ if (priceRatio > 0) {
164
+ require(amount == price, "You can't pay with a larger amount");
165
+ require(token == allowedToken, "Accepts payment in official token only");
166
+ }
167
+
168
+ publicFields[makeAddressString(msg.sender, id)] = text;
169
+
170
+ if (priceRatio == 0) return;
171
+
172
+ IERC20(token).transferFrom(
173
+ msg.sender,
174
+ owner,
175
+ amount
176
+ );
177
+ }
178
+
179
+ function tableRowRead(string memory id, string memory index) view public returns (string memory) {
180
+ return tableFields[makeString(index, id)];
181
+ }
182
+
183
+ function tableRowsCount(string memory id) view public returns (uint256) {
184
+ return tableIndexs[id];
185
+ }
186
+
187
+ function privateRead(string memory id) view public returns (string memory) {
188
+ return privateFields[makeAddressString(msg.sender, id)];
189
+ }
190
+
191
+ function publicRead(string memory id, address _address) view public returns (string memory) {
192
+ address sender = msg.sender;
193
+ if (_address != address(0)) {
194
+ sender = _address;
195
+ }
196
+ return publicFields[makeAddressString(sender, id)];
197
+ }
198
+ }