Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
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
exchain
nebula
Commits
c5a8db93
Unverified
Commit
c5a8db93
authored
Dec 07, 2021
by
smartcontracts
Committed by
GitHub
Dec 07, 2021
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1885 from ethereum-optimism/sc/sdk-interfaces
feat: translate SDK API spec to TS types
parents
31e91fcc
a79d4c10
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
756 additions
and
0 deletions
+756
-0
index.ts
packages/sdk/src/index.ts
+1
-0
cross-chain-messenger.ts
packages/sdk/src/interfaces/cross-chain-messenger.ts
+317
-0
cross-chain-provider.ts
packages/sdk/src/interfaces/cross-chain-provider.ts
+179
-0
index.ts
packages/sdk/src/interfaces/index.ts
+4
-0
l2-provider.ts
packages/sdk/src/interfaces/l2-provider.ts
+51
-0
types.ts
packages/sdk/src/interfaces/types.ts
+204
-0
No files found.
packages/sdk/src/index.ts
View file @
c5a8db93
export
*
from
'
./interfaces
'
packages/sdk/src/interfaces/cross-chain-messenger.ts
0 → 100644
View file @
c5a8db93
import
{
Overrides
,
Signer
}
from
'
ethers
'
import
{
TransactionRequest
,
TransactionResponse
,
}
from
'
@ethersproject/abstract-provider
'
import
{
MessageLike
,
AddressLike
,
NumberLike
,
CrossChainMessageRequest
,
L1ToL2Overrides
,
}
from
'
./types
'
import
{
ICrossChainProvider
}
from
'
./cross-chain-provider
'
/**
* Represents a utility class for making L1/L2 cross-chain transactions.
*/
export
interface
ICrossChainMessenger
{
/**
* Provider that will be used to interact with the L1/L2 system.
*/
provider
:
ICrossChainProvider
/**
* Signer that will carry out L1/L2 transactions.
*/
signer
:
Signer
/**
* Sends a given cross chain message. Where the message is sent depends on the direction attached
* to the message itself.
*
* @param message Cross chain message to send.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the message sending transaction.
*/
sendMessage
(
message
:
CrossChainMessageRequest
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionResponse
>
/**
* Resends a given cross chain message with a different gas limit. Only applies to L1 to L2
* messages. If provided an L2 to L1 message, this function will throw an error.
*
* @param message Cross chain message to resend.
* @param messageGasLimit New gas limit to use for the message.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the message resending transaction.
*/
resendMessage
(
message
:
MessageLike
,
messageGasLimit
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionResponse
>
/**
* Finalizes a cross chain message that was sent from L2 to L1. Only applicable for L2 to L1
* messages. Will throw an error if the message has not completed its challenge period yet.
*
* @param message Message to finalize.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the finalization transaction.
*/
finalizeMessage
(
message
:
MessageLike
,
overrides
?:
Overrides
):
Promise
<
TransactionResponse
>
/**
* Deposits some tokens into the L2 chain.
*
* @param token Address of the token to deposit.
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
*/
depositTokens
(
token
:
AddressLike
,
amount
:
NumberLike
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionResponse
>
/**
* Deposits some ETH into the L2 chain.
*
* @param amount Amount of ETH to deposit (in wei).
* @param overrides Optional transaction overrides.
* @returns Transaction response for the deposit transaction.
*/
depositETH
(
amount
:
NumberLike
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionResponse
>
/**
* Withdraws some tokens back to the L1 chain.
*
* @param token Address of the token to withdraw.
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
*/
withdrawTokens
(
token
:
AddressLike
,
amount
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionResponse
>
/**
* Withdraws some ETH back to the L1 chain.
*
* @param amount Amount of ETH to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction response for the withdraw transaction.
*/
withdrawETH
(
amount
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionResponse
>
/**
* Object that holds the functions that generate transactions to be signed by the user.
* Follows the pattern used by ethers.js.
*/
populateTransaction
:
{
/**
* Generates a transaction that sends a given cross chain message. This transaction can be signed
* and executed by a signer.
*
* @param message Cross chain message to send.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to send the message.
*/
sendMessage
:
(
message
:
CrossChainMessageRequest
,
overrides
?:
L1ToL2Overrides
)
=>
Promise
<
TransactionResponse
>
/**
* Generates a transaction that resends a given cross chain message. Only applies to L1 to L2
* messages. This transaction can be signed and executed by a signer.
*
* @param message Cross chain message to resend.
* @param messageGasLimit New gas limit to use for the message.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to resend the message.
*/
resendMessage
(
message
:
MessageLike
,
messageGasLimit
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
/**
* Generates a message finalization transaction that can be signed and executed. Only
* applicable for L2 to L1 messages. Will throw an error if the message has not completed
* its challenge period yet.
*
* @param message Message to generate the finalization transaction for.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to finalize the message.
*/
finalizeMessage
(
message
:
MessageLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
/**
* Generates a transaction for depositing some tokens into the L2 chain.
*
* @param token Address of the token to deposit.
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
depositTokens
(
token
:
AddressLike
,
amount
:
NumberLike
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionResponse
>
/**
* Generates a transaction for depositing some ETH into the L2 chain.
*
* @param amount Amount of ETH to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the ETH.
*/
depositETH
(
amount
:
NumberLike
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionRequest
>
/**
* Generates a transaction for withdrawing some tokens back to the L1 chain.
*
* @param token Address of the token to withdraw.
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawTokens
(
token
:
AddressLike
,
amount
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
/**
* Generates a transaction for withdrawing some ETH back to the L1 chain.
*
* @param amount Amount of ETH to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawETH
(
amount
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
}
/**
* Object that holds the functions that estimates the gas required for a given transaction.
* Follows the pattern used by ethers.js.
*/
estimateGas
:
{
/**
* Estimates gas required to send a cross chain message.
*
* @param message Cross chain message to send.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to send the message.
*/
sendMessage
:
(
message
:
CrossChainMessageRequest
,
overrides
?:
L1ToL2Overrides
)
=>
Promise
<
TransactionResponse
>
/**
* Estimates gas required to resend a cross chain message. Only applies to L1 to L2 messages.
*
* @param message Cross chain message to resend.
* @param messageGasLimit New gas limit to use for the message.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to resend the message.
*/
resendMessage
(
message
:
MessageLike
,
messageGasLimit
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
/**
* Estimates gas required to finalize a cross chain message. Only applies to L2 to L1 messages.
*
* @param message Message to generate the finalization transaction for.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to finalize the message.
*/
finalizeMessage
(
message
:
MessageLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
/**
* Estimates gas required to deposit some tokens into the L2 chain.
*
* @param token Address of the token to deposit.
* @param amount Amount of the token to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the tokens.
*/
depositTokens
(
token
:
AddressLike
,
amount
:
NumberLike
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionResponse
>
/**
* Estimates gas required to deposit some ETH into the L2 chain.
*
* @param amount Amount of ETH to deposit.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to deposit the ETH.
*/
depositETH
(
amount
:
NumberLike
,
overrides
?:
L1ToL2Overrides
):
Promise
<
TransactionRequest
>
/**
* Estimates gas required to withdraw some tokens back to the L1 chain.
*
* @param token Address of the token to withdraw.
* @param amount Amount of the token to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawTokens
(
token
:
AddressLike
,
amount
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
/**
* Estimates gas required to withdraw some ETH back to the L1 chain.
*
* @param amount Amount of ETH to withdraw.
* @param overrides Optional transaction overrides.
* @returns Transaction that can be signed and executed to withdraw the tokens.
*/
withdrawETH
(
amount
:
NumberLike
,
overrides
?:
Overrides
):
Promise
<
TransactionRequest
>
}
}
packages/sdk/src/interfaces/cross-chain-provider.ts
0 → 100644
View file @
c5a8db93
import
{
BigNumber
}
from
'
ethers
'
import
{
Provider
,
BlockTag
}
from
'
@ethersproject/abstract-provider
'
import
{
MessageLike
,
TransactionLike
,
AddressLike
,
NumberLike
,
CrossChainMessage
,
MessageDirection
,
MessageStatus
,
TokenBridgeMessage
,
OEContracts
,
MessageReceipt
,
}
from
'
./types
'
/**
* Represents the L1/L2 connection. Only handles read requests. If you want to send messages, use
* the CrossChainMessenger contract which takes a CrossChainProvider and a signer as inputs.
*/
export
interface
ICrossChainProvider
{
/**
* Provider connected to the L1 chain.
*/
l1Provider
:
Provider
/**
* Provider connected to the L2 chain.
*/
l2Provider
:
Provider
/**
* Chain ID for the L1 network.
*/
l1ChainId
:
number
/**
* Chain ID for the L2 network.
*/
l2ChainId
:
number
/**
* Contract objects attached to their respective providers and addresses.
*/
contracts
:
OEContracts
/**
* Retrieves all cross chain messages sent within a given transaction.
*
* @param transaction Transaction hash or receipt to find messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* automatically search both directions under the assumption that a transaction hash will only
* exist on one chain. If the hash exists on both chains, will throw an error.
* @returns All cross chain messages sent within the transaction.
*/
getMessagesByTransaction
(
transaction
:
TransactionLike
,
opts
?:
{
direction
?:
MessageDirection
}
):
Promise
<
CrossChainMessage
[]
>
/**
* Retrieves all cross chain messages sent by a particular address.
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All cross chain messages sent by the particular address.
*/
getMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
NumberLike
toBlock
?:
NumberLike
}
):
Promise
<
CrossChainMessage
[]
>
/**
* Finds all cross chain messages that correspond to token deposits or withdrawals sent by a
* particular address. Useful for finding deposits/withdrawals because the sender of the message
* will appear to be the StandardBridge contract and not the actual end user. Returns
*
* @param address Address to search for messages from.
* @param opts Options object.
* @param opts.direction Direction to search for messages in. If not provided, will attempt to
* find all messages in both directions.
* @param opts.fromBlock Block to start searching for messages from. If not provided, will start
* from the first block (block #0).
* @param opts.toBlock Block to stop searching for messages at. If not provided, will stop at the
* latest known block ("latest").
* @returns All token bridge messages sent by the given address.
*/
getTokenBridgeMessagesByAddress
(
address
:
AddressLike
,
opts
?:
{
direction
?:
MessageDirection
fromBlock
?:
BlockTag
toBlock
?:
BlockTag
}
):
Promise
<
TokenBridgeMessage
[]
>
/**
* Retrieves the status of a particular message as an enum.
*
* @param message Cross chain message to check the status of.
* @returns Status of the message.
*/
getMessageStatus
(
message
:
MessageLike
):
Promise
<
MessageStatus
>
/**
* Finds the receipt of the transaction that executed a particular cross chain message.
*
* @param message Message to find the receipt of.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
getMessageReceipt
(
message
:
MessageLike
):
Promise
<
MessageReceipt
>
/**
* Waits for a message to be executed and returns the receipt of the transaction that executed
* the given message.
*
* @param message Message to wait for.
* @param opts Options to pass to the waiting function.
* - `confirmations` (number): Number of transaction confirmations to wait for before returning.
* - `pollIntervalMs` (number): Number of milliseconds to wait between polling for the receipt.
* - `loopsBeforeTimeout` (number): Number of times to poll before timing out.
* @returns CrossChainMessage receipt including receipt of the transaction that relayed the
* given message.
*/
waitForMessageReciept
(
message
:
MessageLike
,
opts
?:
{
confirmations
?:
number
pollIntervalMs
?:
number
loopsBeforeTimeout
?:
number
}
):
Promise
<
MessageReceipt
>
/**
* Estimates the amount of gas required to fully execute a given message. Behavior of this
* function depends on the direction of the message. If the message is an L1 to L2 message,
* then this will estimate the amount of gas required to execute the message on L2. If the
* message is an L2 to L1 message, then this estimate will also include the amount of gas
* required to execute the Merkle Patricia Trie proof on L1.
*
* @param message Message get a gas estimate for.
*/
estimateMessageExecutionGas
(
message
:
MessageLike
):
Promise
<
BigNumber
>
/**
* Returns the estimated amount of time before the message can be executed. When this is a
* message being sent to L1, this will return the estimated time until the message will complete
* its challenge period. When this is a message being sent to L2, this will return the estimated
* amount of time until the message will be picked up and executed on L2.
*
* @param message Message to estimate the time remaining for.
* @returns Estimated amount of time remaining (in seconds) before the message can be executed.
*/
estimateMessageWaitTimeSeconds
(
message
:
MessageLike
):
Promise
<
number
>
/**
* Returns the estimated amount of time before the message can be executed (in L1 blocks).
* When this is a message being sent to L1, this will return the estimated time until the message
* will complete its challenge period. When this is a message being sent to L2, this will return
* the estimated amount of time until the message will be picked up and executed on L2.
*
* @param message Message to estimate the time remaining for.
* @returns Estimated amount of time remaining (in blocks) before the message can be executed.
*/
estimateMessageWaitTimeBlocks
(
message
:
MessageLike
):
Promise
<
number
>
}
packages/sdk/src/interfaces/index.ts
0 → 100644
View file @
c5a8db93
export
*
from
'
./cross-chain-messenger
'
export
*
from
'
./cross-chain-provider
'
export
*
from
'
./l2-provider
'
export
*
from
'
./types
'
packages/sdk/src/interfaces/l2-provider.ts
0 → 100644
View file @
c5a8db93
import
{
Provider
,
TransactionRequest
}
from
'
@ethersproject/abstract-provider
'
import
{
BigNumber
}
from
'
ethers
'
/**
* Represents an extended version of an normal ethers Provider that returns additional L2 info and
* has special functions for L2-specific interactions.
*/
export
interface
L2Provider
extends
Provider
{
/**
* Gets the current L1 (data) gas price.
*
* @returns Current L1 data gas price in wei.
*/
getL1GasPrice
():
Promise
<
BigNumber
>
/**
* Estimates the L1 (data) gas required for a transaction.
*
* @param tx Transaction to estimate L1 gas for.
* @returns Estimated L1 gas.
*/
estimateL1Gas
(
tx
:
TransactionRequest
):
Promise
<
BigNumber
>
/**
* Estimates the L1 (data) gas cost for a transaction in wei by multiplying the estimated L1 gas
* cost by the current L1 gas price.
*
* @param tx Transaction to estimate L1 gas cost for.
* @returns Estimated L1 gas cost.
*/
estimateL1GasCost
(
tx
:
TransactionRequest
):
Promise
<
BigNumber
>
/**
* Estimates the L2 (execution) gas cost for a transaction in wei by multiplying the estimated L1
* gas cost by the current L2 gas price. This is a simple multiplication of the result of
* getGasPrice and estimateGas for the given transaction request.
*
* @param tx Transaction to estimate L2 gas cost for.
* @returns Estimated L2 gas cost.
*/
estimateL2GasCost
(
tx
:
TransactionRequest
):
Promise
<
BigNumber
>
/**
* Estimates the total gas cost for a transaction in wei by adding the estimated the L1 gas cost
* and the estimated L2 gas cost.
*
* @param tx Transaction to estimate total gas cost for.
* @returns Estimated total gas cost.
*/
estimateTotalGasCost
(
tx
:
TransactionRequest
):
Promise
<
BigNumber
>
}
packages/sdk/src/interfaces/types.ts
0 → 100644
View file @
c5a8db93
import
{
Provider
,
TransactionReceipt
,
TransactionResponse
,
}
from
'
@ethersproject/abstract-provider
'
import
{
Signer
}
from
'
@ethersproject/abstract-signer
'
import
{
Contract
,
BigNumber
,
Overrides
}
from
'
ethers
'
/**
* Represents Optimistic Ethereum contracts, assumed to be connected to their appropriate
* providers and addresses.
*/
export
interface
OEContracts
{
/**
* L1 contract references.
*/
l1
:
{
AddressManager
:
Contract
L1CrossDomainMessenger
:
Contract
L1StandardBridge
:
Contract
StateCommitmentChain
:
Contract
CanonicalTransactionChain
:
Contract
BondManager
:
Contract
}
/**
* L2 contract references.
*/
l2
:
{
L2CrossDomainMessenger
:
Contract
L2StandardBridge
:
Contract
OVM_L1BlockNumber
:
Contract
OVM_L2ToL1MessagePasser
:
Contract
OVM_DeployerWhitelist
:
Contract
OVM_ETH
:
Contract
OVM_GasPriceOracle
:
Contract
OVM_SequencerFeeVault
:
Contract
WETH
:
Contract
}
}
/**
* Enum describing the status of a message.
*/
export
enum
MessageStatus
{
/**
* Message is an L1 to L2 message and has not been processed by the L2.
*/
UNCONFIRMED_L1_TO_L2_MESSAGE
,
/**
* Message is an L2 to L1 message and no state root has been published yet.
*/
STATE_ROOT_NOT_PUBLISHED
,
/**
* Message is an L2 to L1 message and awaiting the challenge period.
*/
IN_CHALLENGE_PERIOD
,
/**
* Message is ready to be relayed.
*/
READY_FOR_RELAY
,
/**
* Message has been relayed.
*/
RELAYED
,
}
/**
* Enum describing the direction of a message.
*/
export
enum
MessageDirection
{
L1_TO_L2
,
L2_TO_L1
,
}
/**
* Partial message that needs to be signed and executed by a specific signer.
*/
export
interface
CrossChainMessageRequest
{
direction
:
MessageDirection
target
:
string
message
:
string
l2GasLimit
:
NumberLike
}
/**
* Describes a message that is sent between L1 and L2. Direction determines where the message was
* sent from and where it's being sent to.
*/
export
interface
CrossChainMessage
{
direction
:
MessageDirection
sender
:
string
target
:
string
message
:
string
messageNonce
:
number
}
/**
* Describes a token withdrawal or deposit, along with the underlying raw cross chain message
* behind the deposit or withdrawal.
*/
export
interface
TokenBridgeMessage
{
direction
:
MessageDirection
from
:
string
to
:
string
l1Token
:
string
l2Token
:
string
amount
:
BigNumber
raw
:
CrossChainMessage
}
/**
* Enum describing the status of a CrossDomainMessage message receipt.
*/
export
enum
MessageReceiptStatus
{
RELAYED_SUCCEEDED
,
RELAYED_FAILED
,
}
/**
* CrossDomainMessage receipt.
*/
export
interface
MessageReceipt
{
messageHash
:
string
receiptStatus
:
MessageReceiptStatus
transactionReceipt
:
TransactionReceipt
}
/**
* Header for a state root batch.
*/
export
interface
StateRootBatchHeader
{
batchIndex
:
BigNumber
batchRoot
:
string
batchSize
:
BigNumber
prevTotalElements
:
BigNumber
extraData
:
string
}
/**
* State root batch, including header and actual state roots.
*/
export
interface
StateRootBatch
{
header
:
StateRootBatchHeader
stateRoots
:
string
[]
}
/**
* Utility type for deep partials.
*/
export
type
DeepPartial
<
T
>
=
{
[
P
in
keyof
T
]?:
DeepPartial
<
T
[
P
]
>
}
/**
* Extended Ethers overrides object with an l2GasLimit field.
* Only meant to be used for L1 to L2 messages, since L2 to L1 messages don't have a specified gas
* limit field (gas used depends on the amount of gas provided).
*/
export
type
L1ToL2Overrides
=
Overrides
&
{
l2GasLimit
:
NumberLike
}
/**
* Stuff that can be coerced into a transaction.
*/
export
type
TransactionLike
=
string
|
TransactionReceipt
|
TransactionResponse
/**
* Stuff that can be coerced into a message.
*/
export
type
MessageLike
=
|
CrossChainMessage
|
TransactionLike
|
TokenBridgeMessage
/**
* Stuff that can be coerced into a provider.
*/
export
type
ProviderLike
=
string
|
Provider
/**
* Stuff that can be coerced into a signer.
*/
export
type
SignerLike
=
string
|
Signer
/**
* Stuff that can be coerced into a signer or provider.
*/
export
type
SignerOrProviderLike
=
SignerLike
|
ProviderLike
/**
* Stuff that can be coerced into an address.
*/
export
type
AddressLike
=
string
|
Contract
/**
* Stuff that can be coerced into a number.
*/
export
type
NumberLike
=
string
|
number
|
BigNumber
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