Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
F
frontend
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
vicotor
frontend
Commits
93bb45df
Commit
93bb45df
authored
Feb 01, 2023
by
tom
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
submit form to api
parent
81c3dac7
Changes
13
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
219 additions
and
27 deletions
+219
-27
nodeFetch.ts
lib/api/nodeFetch.ts
+16
-2
resources.ts
lib/api/resources.ts
+3
-0
useFetch.tsx
lib/hooks/useFetch.tsx
+18
-4
context.tsx
lib/socket/context.tsx
+1
-0
types.ts
lib/socket/types.ts
+3
-1
useSocketChannel.tsx
lib/socket/useSocketChannel.tsx
+6
-2
contract.ts
types/api/contract.ts
+1
-1
ContractVerificationForm.pw.tsx
ui/contractVerification/ContractVerificationForm.pw.tsx
+7
-6
ContractVerificationForm.tsx
ui/contractVerification/ContractVerificationForm.tsx
+50
-5
ContractVerificationFieldMethod.tsx
...ctVerification/fields/ContractVerificationFieldMethod.tsx
+1
-1
types.ts
ui/contractVerification/types.ts
+3
-1
utils.ts
ui/contractVerification/utils.ts
+106
-1
ContractVerification.tsx
ui/pages/ContractVerification.tsx
+4
-3
No files found.
lib/api/nodeFetch.ts
View file @
93bb45df
...
...
@@ -13,9 +13,10 @@ export default function fetchFactory(
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
return
function
fetch
(
url
:
string
,
init
?:
RequestInit
):
Promise
<
Response
>
{
const
incomingContentType
=
_req
.
headers
[
'
content-type
'
];
const
headers
=
{
accept
:
'
application/json
'
,
'
content-type
'
:
'
application/json
'
,
'
content-type
'
:
incomingContentType
?.
match
(
/^multipart
\/
form-data/
)
?
incomingContentType
:
'
application/json
'
,
cookie
:
`
${
cookies
.
NAMES
.
API_TOKEN
}
=
${
_req
.
cookies
[
cookies
.
NAMES
.
API_TOKEN
]
}
`
,
};
...
...
@@ -25,10 +26,23 @@ export default function fetchFactory(
req
:
_req
,
});
const
body
=
(()
=>
{
const
_body
=
init
?.
body
;
if
(
!
_body
)
{
return
;
}
if
(
typeof
_body
===
'
string
'
)
{
return
_body
;
}
return
JSON
.
stringify
(
_body
);
})();
return
nodeFetch
(
url
,
{
...
init
,
headers
,
body
:
init
?.
body
?
JSON
.
stringify
(
init
.
body
)
:
undefined
,
body
,
});
};
}
lib/api/resources.ts
View file @
93bb45df
...
...
@@ -207,6 +207,9 @@ export const RESOURCES = {
contract_verification_config
:
{
path
:
'
/api/v2/smart-contracts/verification/config
'
,
},
contract_verification_via
:
{
path
:
'
/api/v2/smart-contracts/:id/verification/via/:method
'
,
},
// TOKEN
token
:
{
...
...
lib/hooks/useFetch.tsx
View file @
93bb45df
...
...
@@ -11,7 +11,7 @@ export interface Params {
method
?:
RequestInit
[
'
method
'
];
headers
?:
RequestInit
[
'
headers
'
];
signal
?:
RequestInit
[
'
signal
'
];
body
?:
Record
<
string
,
unknown
>
;
body
?:
Record
<
string
,
unknown
>
|
FormData
;
credentials
?:
RequestCredentials
;
}
...
...
@@ -20,13 +20,27 @@ export default function useFetch() {
const
{
token
}
=
queryClient
.
getQueryData
<
CsrfData
>
(
getResourceKey
(
'
csrf
'
))
||
{};
return
React
.
useCallback
(<
Success
,
Error
>
(path: string, params?: Params): Promise
<
Success
|
ResourceError
<
Error
>
>
=
>
{
const
hasBody
=
params
?.
method
&&
!
[
'
GET
'
,
'
HEAD
'
].
includes
(
params
.
method
);
const
_body
=
params
?.
body
;
const
isFormData
=
_body
instanceof
FormData
;
const
isBodyAllowed
=
params
?.
method
&&
!
[
'
GET
'
,
'
HEAD
'
].
includes
(
params
.
method
);
const
body
:
FormData
|
string
|
undefined
=
(()
=>
{
if
(
!
isBodyAllowed
)
{
return
;
}
if
(
isFormData
)
{
return
_body
;
}
return
JSON
.
stringify
({
...
_body
,
_csrf_token
:
token
});
})();
const
reqParams
=
{
...
params
,
body
:
hasBody
?
JSON
.
stringify
({
...
params
.
body
,
_csrf_token
:
token
})
:
undefined
,
body
,
headers
:
{
...(
hasBody
?
{
'
Content-type
'
:
'
application/json
'
}
:
undefined
),
...(
isBodyAllowed
&&
!
isFormData
?
{
'
Content-type
'
:
'
application/json
'
}
:
undefined
),
...
params
?.
headers
,
},
};
...
...
lib/socket/context.tsx
View file @
93bb45df
// https://hexdocs.pm/phoenix/js/
import
type
{
SocketConnectOption
}
from
'
phoenix
'
;
import
{
Socket
}
from
'
phoenix
'
;
import
React
,
{
useEffect
,
useState
}
from
'
react
'
;
...
...
lib/socket/types.ts
View file @
93bb45df
...
...
@@ -19,6 +19,7 @@ SocketMessage.AddressTxs |
SocketMessage
.
AddressTxsPending
|
SocketMessage
.
AddressTokenTransfer
|
SocketMessage
.
TokenTransfers
|
SocketMessage
.
ContractVerification
|
SocketMessage
.
Unknown
;
interface
SocketMessageParamsGeneric
<
Event
extends
string
|
undefined
,
Payload
extends
object
|
unknown
>
{
...
...
@@ -43,6 +44,7 @@ export namespace SocketMessage {
export
type
AddressTxs
=
SocketMessageParamsGeneric
<
'
transaction
'
,
{
transaction
:
Transaction
}
>
;
export
type
AddressTxsPending
=
SocketMessageParamsGeneric
<
'
pending_transaction
'
,
{
transaction
:
Transaction
}
>
;
export
type
AddressTokenTransfer
=
SocketMessageParamsGeneric
<
'
token_transfer
'
,
{
token_transfer
:
TokenTransfer
}
>
;
export
type
TokenTransfers
=
SocketMessageParamsGeneric
<
'
token_transfer
'
,
{
token_transfer
:
number
}
>
export
type
TokenTransfers
=
SocketMessageParamsGeneric
<
'
token_transfer
'
,
{
token_transfer
:
number
}
>
;
export
type
ContractVerification
=
SocketMessageParamsGeneric
<
'
verification
'
,
unknown
>
;
export
type
Unknown
=
SocketMessageParamsGeneric
<
undefined
,
unknown
>
;
}
lib/socket/useSocketChannel.tsx
View file @
93bb45df
...
...
@@ -60,7 +60,11 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
}
else
{
ch
=
socket
.
channel
(
topic
);
CHANNEL_REGISTRY
[
topic
]
=
ch
;
ch
.
join
().
receive
(
'
ok
'
,
(
message
)
=>
onJoinRef
.
current
?.(
ch
,
message
));
ch
.
join
()
.
receive
(
'
ok
'
,
(
message
)
=>
onJoinRef
.
current
?.(
ch
,
message
))
.
receive
(
'
error
'
,
()
=>
{
onSocketError
?.();
});
}
setChannel
(
ch
);
...
...
@@ -70,7 +74,7 @@ export default function useSocketChannel({ topic, params, isDisabled, onJoin, on
delete
CHANNEL_REGISTRY
[
topic
];
setChannel
(
undefined
);
};
},
[
socket
,
topic
,
params
,
isDisabled
]);
},
[
socket
,
topic
,
params
,
isDisabled
,
onSocketError
]);
return
channel
;
}
types/api/contract.ts
View file @
93bb45df
...
...
@@ -116,7 +116,7 @@ export type SmartContractQueryMethodRead = SmartContractQueryMethodReadSuccess |
// VERIFICATION
export
type
SmartContractVerificationMethod
=
'
flattened_code
'
|
'
standard_input
'
|
'
sourcify
'
|
'
multi_part
'
|
'
vyper_
multi_part
'
;
export
type
SmartContractVerificationMethod
=
'
flattened_code
'
|
'
standard_input
'
|
'
sourcify
'
|
'
multi_part
'
|
'
vyper_
code
'
;
export
interface
SmartContractVerificationConfigRaw
{
solidity_compiler_versions
:
Array
<
string
>
;
...
...
ui/contractVerification/ContractVerificationForm.pw.tsx
View file @
93bb45df
...
...
@@ -13,6 +13,7 @@ const hooksConfig = {
},
};
const
hash
=
'
0x2F99338637F027CFB7494E46B49987457beCC6E3
'
;
const
formConfig
:
SmartContractVerificationConfig
=
{
solidity_compiler_versions
:
[
'
v0.8.17+commit.8df45f5f
'
,
...
...
@@ -32,7 +33,7 @@ const formConfig: SmartContractVerificationConfig = {
'
standard_input
'
,
'
sourcify
'
,
'
multi_part
'
,
'
vyper_
multi_part
'
,
'
vyper_
code
'
,
],
vyper_compiler_versions
:
[
'
v0.3.7+commit.6020b8bb
'
,
...
...
@@ -54,7 +55,7 @@ const formConfig: SmartContractVerificationConfig = {
test
(
'
flatten source code method +@dark-mode +@mobile
'
,
async
({
mount
,
page
})
=>
{
const
component
=
await
mount
(
<
TestApp
>
<
ContractVerificationForm
config=
{
formConfig
}
/>
<
ContractVerificationForm
config=
{
formConfig
}
hash=
{
hash
}
/>
</
TestApp
>,
{
hooksConfig
},
);
...
...
@@ -70,7 +71,7 @@ test('flatten source code method +@dark-mode +@mobile', async({ mount, page }) =
test
(
'
standard input json method
'
,
async
({
mount
,
page
})
=>
{
const
component
=
await
mount
(
<
TestApp
>
<
ContractVerificationForm
config=
{
formConfig
}
/>
<
ContractVerificationForm
config=
{
formConfig
}
hash=
{
hash
}
/>
</
TestApp
>,
{
hooksConfig
},
);
...
...
@@ -83,7 +84,7 @@ test('standard input json method', async({ mount, page }) => {
test
(
'
sourcify method +@dark-mode +@mobile
'
,
async
({
mount
,
page
})
=>
{
const
component
=
await
mount
(
<
TestApp
>
<
ContractVerificationForm
config=
{
formConfig
}
/>
<
ContractVerificationForm
config=
{
formConfig
}
hash=
{
hash
}
/>
</
TestApp
>,
{
hooksConfig
},
);
...
...
@@ -102,7 +103,7 @@ test('sourcify method +@dark-mode +@mobile', async({ mount, page }) => {
test
(
'
multi-part files method
'
,
async
({
mount
,
page
})
=>
{
const
component
=
await
mount
(
<
TestApp
>
<
ContractVerificationForm
config=
{
formConfig
}
/>
<
ContractVerificationForm
config=
{
formConfig
}
hash=
{
hash
}
/>
</
TestApp
>,
{
hooksConfig
},
);
...
...
@@ -115,7 +116,7 @@ test('multi-part files method', async({ mount, page }) => {
test
(
'
vyper contract method
'
,
async
({
mount
,
page
})
=>
{
const
component
=
await
mount
(
<
TestApp
>
<
ContractVerificationForm
config=
{
formConfig
}
/>
<
ContractVerificationForm
config=
{
formConfig
}
hash=
{
hash
}
/>
</
TestApp
>,
{
hooksConfig
},
);
...
...
ui/contractVerification/ContractVerificationForm.tsx
View file @
93bb45df
...
...
@@ -4,9 +4,12 @@ import type { SubmitHandler } from 'react-hook-form';
import
{
useForm
,
FormProvider
}
from
'
react-hook-form
'
;
import
type
{
FormFields
}
from
'
./types
'
;
import
type
{
SocketMessage
}
from
'
lib/socket/types
'
;
import
type
{
SmartContractVerificationMethod
,
SmartContractVerificationConfig
}
from
'
types/api/contract
'
;
import
delay
from
'
lib/delay
'
;
import
useApiFetch
from
'
lib/api/useApiFetch
'
;
import
useSocketChannel
from
'
lib/socket/useSocketChannel
'
;
import
useSocketMessage
from
'
lib/socket/useSocketMessage
'
;
import
ContractVerificationFieldMethod
from
'
./fields/ContractVerificationFieldMethod
'
;
import
ContractVerificationFlattenSourceCode
from
'
./methods/ContractVerificationFlattenSourceCode
'
;
...
...
@@ -14,21 +17,23 @@ import ContractVerificationMultiPartFile from './methods/ContractVerificationMul
import
ContractVerificationSourcify
from
'
./methods/ContractVerificationSourcify
'
;
import
ContractVerificationStandardInput
from
'
./methods/ContractVerificationStandardInput
'
;
import
ContractVerificationVyperContract
from
'
./methods/ContractVerificationVyperContract
'
;
import
{
prepareRequestBody
,
METHOD_TO_ENDPOINT_MAP
}
from
'
./utils
'
;
const
METHOD_COMPONENTS
=
{
flattened_code
:
<
ContractVerificationFlattenSourceCode
/>,
standard_input
:
<
ContractVerificationStandardInput
/>,
sourcify
:
<
ContractVerificationSourcify
/>,
multi_part
:
<
ContractVerificationMultiPartFile
/>,
vyper_
multi_part
:
<
ContractVerificationVyperContract
/>,
vyper_
code
:
<
ContractVerificationVyperContract
/>,
};
interface
Props
{
method
?:
SmartContractVerificationMethod
;
config
:
SmartContractVerificationConfig
;
hash
:
string
;
}
const
ContractVerificationForm
=
({
method
:
methodFromQuery
,
config
}:
Props
)
=>
{
const
ContractVerificationForm
=
({
method
:
methodFromQuery
,
config
,
hash
}:
Props
)
=>
{
const
formApi
=
useForm
<
FormFields
>
({
mode
:
'
onBlur
'
,
defaultValues
:
{
...
...
@@ -36,15 +41,55 @@ const ContractVerificationForm = ({ method: methodFromQuery, config }: Props) =>
},
});
const
{
control
,
handleSubmit
,
watch
,
formState
}
=
formApi
;
const
submitPromiseResolver
=
React
.
useRef
<
(
value
:
unknown
)
=>
void
>
();
const
apiFetch
=
useApiFetch
();
const
onFormSubmit
:
SubmitHandler
<
FormFields
>
=
React
.
useCallback
(
async
(
data
)
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'
__>__
'
,
data
);
await
delay
(
5
_000
);
const
body
=
prepareRequestBody
(
data
);
try
{
await
apiFetch
(
'
contract_verification_via
'
,
{
pathParams
:
{
method
:
METHOD_TO_ENDPOINT_MAP
[
data
.
method
],
id
:
hash
},
fetchParams
:
{
method
:
'
POST
'
,
body
,
},
});
}
catch
(
error
)
{
return
;
}
return
new
Promise
((
resolve
)
=>
{
submitPromiseResolver
.
current
=
resolve
;
});
},
[
apiFetch
,
hash
]);
const
handleNewSocketMessage
:
SocketMessage
.
ContractVerification
[
'
handler
'
]
=
React
.
useCallback
((
payload
)
=>
{
// eslint-disable-next-line no-console
console
.
log
(
'
__>__
'
,
payload
);
},
[]);
const
method
=
watch
(
'
method
'
);
const
handleSocketError
=
React
.
useCallback
(()
=>
{
submitPromiseResolver
.
current
?.(
null
);
},
[]);
const
channel
=
useSocketChannel
({
topic
:
`address:
${
hash
}
`
,
onSocketClose
:
handleSocketError
,
onSocketError
:
handleSocketError
,
isDisabled
:
!
formState
.
isSubmitting
,
});
useSocketMessage
({
channel
,
event
:
'
verification
'
,
handler
:
handleNewSocketMessage
,
});
const
method
=
watch
(
'
method
'
);
const
content
=
METHOD_COMPONENTS
[
method
]
||
null
;
return
(
...
...
ui/contractVerification/fields/ContractVerificationFieldMethod.tsx
View file @
93bb45df
...
...
@@ -87,7 +87,7 @@ const ContractVerificationFieldMethod = ({ control, isDisabled, methods }: Props
);
case
'
multi_part
'
:
return
'
Via multi-part files
'
;
case
'
vyper_
multi_part
'
:
case
'
vyper_
code
'
:
return
'
Vyper contract
'
;
default
:
...
...
ui/contractVerification/types.ts
View file @
93bb45df
...
...
@@ -22,6 +22,7 @@ export interface FormFieldsStandardInput {
name
:
string
;
compiler
:
Option
;
sources
:
Array
<
File
>
;
constructor_args
:
boolean
;
}
export
interface
FormFieldsSourcify
{
...
...
@@ -36,10 +37,11 @@ export interface FormFieldsMultiPartFile {
is_optimization_enabled
:
boolean
;
optimization_runs
:
string
;
sources
:
Array
<
File
>
;
libraries
:
Array
<
ContractLibrary
>
;
}
export
interface
FormFieldsVyperContract
{
method
:
'
vyper_
multi_part
'
;
method
:
'
vyper_
code
'
;
name
:
string
;
compiler
:
Option
;
code
:
string
;
...
...
ui/contractVerification/utils.ts
View file @
93bb45df
import
type
{
ContractLibrary
,
FormFields
}
from
'
./types
'
;
import
type
{
SmartContractVerificationMethod
}
from
'
types/api/contract
'
;
import
type
{
Params
as
FetchParams
}
from
'
lib/hooks/useFetch
'
;
export
const
SUPPORTED_VERIFICATION_METHODS
:
Array
<
SmartContractVerificationMethod
>
=
[
'
flattened_code
'
,
'
standard_input
'
,
'
sourcify
'
,
'
multi_part
'
,
'
vyper_
multi_part
'
,
'
vyper_
code
'
,
];
export
const
METHOD_TO_ENDPOINT_MAP
:
Record
<
SmartContractVerificationMethod
,
string
>
=
{
flattened_code
:
'
flattened-code
'
,
standard_input
:
'
standard-input
'
,
sourcify
:
'
sourcify
'
,
multi_part
:
'
multi-part
'
,
vyper_code
:
'
vyper-code
'
,
};
export
function
isValidVerificationMethod
(
method
?:
string
):
method
is
SmartContractVerificationMethod
{
return
method
&&
SUPPORTED_VERIFICATION_METHODS
.
includes
(
method
as
SmartContractVerificationMethod
)
?
true
:
false
;
}
export
function
sortVerificationMethods
(
methodA
:
SmartContractVerificationMethod
,
methodB
:
SmartContractVerificationMethod
)
{
const
indexA
=
SUPPORTED_VERIFICATION_METHODS
.
indexOf
(
methodA
);
const
indexB
=
SUPPORTED_VERIFICATION_METHODS
.
indexOf
(
methodB
);
if
(
indexA
>
indexB
)
{
return
1
;
}
if
(
indexA
<
indexB
)
{
return
-
1
;
}
return
0
;
}
export
function
prepareRequestBody
(
data
:
FormFields
):
FetchParams
[
'
body
'
]
{
switch
(
data
.
method
)
{
case
'
flattened_code
'
:
{
return
{
compiler_version
:
data
.
compiler
.
value
,
source_code
:
data
.
code
,
is_optimization_enabled
:
data
.
is_optimization_enabled
,
optimization_runs
:
data
.
optimization_runs
,
contract_name
:
data
.
name
,
libraries
:
reduceLibrariesArray
(
data
.
libraries
),
evm_version
:
data
.
evm_version
.
value
,
autodetect_constructor_args
:
data
.
constructor_args
,
};
}
case
'
standard_input
'
:
{
const
body
=
new
FormData
();
body
.
set
(
'
compiler_version
'
,
data
.
compiler
.
value
);
body
.
set
(
'
contract_name
'
,
data
.
name
);
body
.
set
(
'
autodetect_constructor_args
'
,
String
(
Boolean
(
data
.
constructor_args
)));
addFilesToFormData
(
body
,
data
.
sources
);
return
body
;
}
case
'
sourcify
'
:
{
const
body
=
new
FormData
();
addFilesToFormData
(
body
,
data
.
sources
);
return
body
;
}
case
'
multi_part
'
:
{
const
body
=
new
FormData
();
body
.
set
(
'
compiler_version
'
,
data
.
compiler
.
value
);
body
.
set
(
'
evm_version
'
,
data
.
evm_version
.
value
);
body
.
set
(
'
is_optimization_enabled
'
,
String
(
Boolean
(
data
.
is_optimization_enabled
)));
data
.
is_optimization_enabled
&&
body
.
set
(
'
optimization_runs
'
,
data
.
optimization_runs
);
const
libraries
=
reduceLibrariesArray
(
data
.
libraries
);
libraries
&&
body
.
set
(
'
libraries
'
,
JSON
.
stringify
(
libraries
));
addFilesToFormData
(
body
,
data
.
sources
);
return
body
;
}
case
'
vyper_code
'
:
{
return
{
compiler_version
:
data
.
compiler
.
value
,
source_code
:
data
.
code
,
contract_name
:
data
.
name
,
constructor_args
:
data
.
abi_encoded_args
,
};
}
default
:
{
return
{};
}
}
}
function
reduceLibrariesArray
(
libraries
:
Array
<
ContractLibrary
>
|
undefined
)
{
if
(
!
libraries
||
libraries
.
length
===
0
)
{
return
;
}
return
libraries
.
reduce
<
Record
<
string
,
string
>>
((
result
,
item
)
=>
{
result
[
item
.
name
]
=
item
.
address
;
return
result
;
},
{});
}
function
addFilesToFormData
(
body
:
FormData
,
files
:
Array
<
File
>
)
{
for
(
let
index
=
0
;
index
<
files
.
length
;
index
++
)
{
const
file
=
files
[
index
];
body
.
set
(
`files[
${
index
}
]`
,
file
,
file
.
name
);
}
}
ui/pages/ContractVerification.tsx
View file @
93bb45df
...
...
@@ -9,7 +9,7 @@ import { useAppContext } from 'lib/appContext';
import
isBrowser
from
'
lib/isBrowser
'
;
import
link
from
'
lib/link/link
'
;
import
ContractVerificationForm
from
'
ui/contractVerification/ContractVerificationForm
'
;
import
{
isValidVerificationMethod
}
from
'
ui/contractVerification/utils
'
;
import
{
isValidVerificationMethod
,
sortVerificationMethods
}
from
'
ui/contractVerification/utils
'
;
import
Address
from
'
ui/shared/address/Address
'
;
import
AddressIcon
from
'
ui/shared/address/AddressIcon
'
;
import
ContentLoader
from
'
ui/shared/ContentLoader
'
;
...
...
@@ -35,7 +35,7 @@ const ContractVerification = () => {
const
_data
=
data
as
SmartContractVerificationConfigRaw
;
return
{
...
_data
,
verification_options
:
_data
.
verification_options
.
filter
(
isValidVerificationMethod
),
verification_options
:
_data
.
verification_options
.
filter
(
isValidVerificationMethod
)
.
sort
(
sortVerificationMethods
)
,
};
},
enabled
:
Boolean
(
hash
),
...
...
@@ -51,7 +51,7 @@ const ContractVerification = () => {
},
[
]);
const
content
=
(()
=>
{
if
(
configQuery
.
isError
)
{
if
(
configQuery
.
isError
||
!
hash
)
{
return
<
DataFetchAlert
/>;
}
...
...
@@ -63,6 +63,7 @@ const ContractVerification = () => {
<
ContractVerificationForm
method=
{
method
&&
configQuery
.
data
.
verification_options
.
includes
(
method
)
?
method
:
undefined
}
config=
{
configQuery
.
data
}
hash=
{
hash
}
/>
);
})();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment