diff --git a/.github/workflows/main-pr.yml b/.github/workflows/main-pr.yml
index a0e5d90d1..19e92ce7d 100644
--- a/.github/workflows/main-pr.yml
+++ b/.github/workflows/main-pr.yml
@@ -85,3 +85,20 @@ jobs:
push: true
tags: |
ghcr.io/${{ github.repository }}/clearnode:${{ steps.sha.outputs.short_sha }}
+
+ build-and-preview-docs-firebase:
+ name: Deploy to Firebase Hosting on PR
+ if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - run: npm install && npm run build
+ working-directory: erc7824-docs
+
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ # repoToken: ${{ secrets.GITHUB_TOKEN }}
+ firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_ERC7824 }}
+ projectId: erc7824
+ entryPoint: ./erc7824-docs
diff --git a/.github/workflows/main-push.yml b/.github/workflows/main-push.yml
index eec996be3..b9f70d1b0 100644
--- a/.github/workflows/main-push.yml
+++ b/.github/workflows/main-push.yml
@@ -215,3 +215,20 @@ jobs:
⚠️ RC build or deployment was cancelled!
${{github.event.head_commit.message}}
SLACK_FOOTER: 'Nitrolite CI/CD Pipeline'
+
+ build-and-deploy-docs-firebase:
+ name: Deploy to Firebase Hosting on merge
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - run: npm install && npm run build
+ working-directory: erc7824-docs
+
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ # repoToken: ${{ secrets.GITHUB_TOKEN }}
+ firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_ERC7824 }}
+ channelId: live
+ projectId: erc7824
+ entryPoint: ./erc7824-docs
diff --git a/clearnode/channel_service.go b/clearnode/channel_service.go
index a98e37ad2..01622100c 100644
--- a/clearnode/channel_service.go
+++ b/clearnode/channel_service.go
@@ -7,7 +7,6 @@ import (
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/crypto"
"github.com/shopspring/decimal"
"gorm.io/gorm"
)
@@ -114,13 +113,12 @@ func (s *ChannelService) RequestResize(logger Logger, params *ResizeChannelParam
// 6) Encode & sign the new state
channelIDHash := common.HexToHash(channel.ChannelID)
- encodedState, err := nitrolite.EncodeState(channelIDHash, nitrolite.IntentRESIZE, big.NewInt(int64(channel.Version)+1), encodedIntentions, allocations)
+ packedState, err := nitrolite.PackState(channelIDHash, nitrolite.IntentRESIZE, big.NewInt(int64(channel.Version)+1), encodedIntentions, allocations)
if err != nil {
- logger.Error("failed to encode state hash", "error", err)
- return ResizeChannelResponse{}, RPCErrorf("failed to encode state hash")
+ logger.Error("failed to pack state", "error", err)
+ return ResizeChannelResponse{}, RPCErrorf("failed to pack state")
}
- stateHash := crypto.Keccak256Hash(encodedState).Hex()
- sig, err := s.signer.NitroSign(encodedState)
+ sig, err := s.signer.Sign(packedState)
if err != nil {
logger.Error("failed to sign state", "error", err)
return ResizeChannelResponse{}, RPCErrorf("failed to sign state")
@@ -131,12 +129,7 @@ func (s *ChannelService) RequestResize(logger Logger, params *ResizeChannelParam
Intent: uint8(nitrolite.IntentRESIZE),
Version: channel.Version + 1,
StateData: hexutil.Encode(encodedIntentions),
- StateHash: stateHash,
- Signature: Signature{
- V: sig.V,
- R: hexutil.Encode(sig.R[:]),
- S: hexutil.Encode(sig.S[:]),
- },
+ Signature: sig,
}
for _, alloc := range allocations {
@@ -217,13 +210,12 @@ func (s *ChannelService) RequestClose(logger Logger, params *CloseChannelParams,
logger.Error("failed to decode state data hex", "error", err)
return CloseChannelResponse{}, RPCErrorf("failed to decode state data hex")
}
- encodedState, err := nitrolite.EncodeState(common.HexToHash(channel.ChannelID), nitrolite.IntentFINALIZE, big.NewInt(int64(channel.Version)+1), stateDataBytes, allocations)
+ packedState, err := nitrolite.PackState(common.HexToHash(channel.ChannelID), nitrolite.IntentFINALIZE, big.NewInt(int64(channel.Version)+1), stateDataBytes, allocations)
if err != nil {
- logger.Error("failed to encode state hash", "error", err)
- return CloseChannelResponse{}, RPCErrorf("failed to encode state hash")
+ logger.Error("failed to pack state", "error", err)
+ return CloseChannelResponse{}, RPCErrorf("failed to pack state")
}
- stateHash := crypto.Keccak256Hash(encodedState).Hex()
- sig, err := s.signer.NitroSign(encodedState)
+ sig, err := s.signer.Sign(packedState)
if err != nil {
logger.Error("failed to sign state", "error", err)
return CloseChannelResponse{}, RPCErrorf("failed to sign state")
@@ -234,12 +226,7 @@ func (s *ChannelService) RequestClose(logger Logger, params *CloseChannelParams,
Intent: uint8(nitrolite.IntentFINALIZE),
Version: channel.Version + 1,
StateData: stateDataHex,
- StateHash: stateHash,
- Signature: Signature{
- V: sig.V,
- R: hexutil.Encode(sig.R[:]),
- S: hexutil.Encode(sig.S[:]),
- },
+ Signature: sig,
}
for _, alloc := range allocations {
diff --git a/clearnode/custody.go b/clearnode/custody.go
index dd4d13136..80ac6c95c 100644
--- a/clearnode/custody.go
+++ b/clearnode/custody.go
@@ -117,7 +117,7 @@ func (c *Custody) Join(channelID string, lastStateData []byte) (common.Hash, err
// The broker will always join as participant with index 1 (second participant)
index := big.NewInt(1)
- sig, err := c.signer.NitroSign(lastStateData)
+ sig, err := c.signer.Sign(lastStateData)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to sign data: %w", err)
}
@@ -288,7 +288,7 @@ func (c *Custody) handleCreated(logger Logger, ev *nitrolite.CustodyCreated) {
return
}
- encodedState, err := nitrolite.EncodeState(ev.ChannelId, nitrolite.IntentINITIALIZE, big.NewInt(0), ev.Initial.Data, ev.Initial.Allocations)
+ encodedState, err := nitrolite.PackState(ev.ChannelId, nitrolite.IntentINITIALIZE, big.NewInt(0), ev.Initial.Data, ev.Initial.Allocations)
if err != nil {
logger.Error("error encoding state hash", "error", err)
return
diff --git a/clearnode/custody_test.go b/clearnode/custody_test.go
index 7ce16df44..88421e844 100644
--- a/clearnode/custody_test.go
+++ b/clearnode/custody_test.go
@@ -135,7 +135,7 @@ func createMockCreatedEvent(t *testing.T, signer *Signer, token string, amount *
Version: big.NewInt(0),
Data: []byte{},
Allocations: allocation,
- Sigs: []nitrolite.Signature{},
+ Sigs: [][]byte{},
}
event := &nitrolite.CustodyCreated{
@@ -189,7 +189,7 @@ func createMockClosedEvent(t *testing.T, signer *Signer, token string, amount *b
Version: big.NewInt(1),
Data: []byte{},
Allocations: allocation,
- Sigs: []nitrolite.Signature{},
+ Sigs: [][]byte{},
}
event := &nitrolite.CustodyClosed{
@@ -226,7 +226,7 @@ func createMockChallengedEvent(t *testing.T, signer *Signer, token string, amoun
Version: big.NewInt(2),
Data: []byte{},
Allocations: allocation,
- Sigs: []nitrolite.Signature{},
+ Sigs: [][]byte{},
}
event := &nitrolite.CustodyChallenged{
@@ -314,7 +314,7 @@ func TestHandleCreatedEvent(t *testing.T) {
Version: big.NewInt(0),
Data: []byte{},
Allocations: allocation,
- Sigs: []nitrolite.Signature{},
+ Sigs: [][]byte{},
}
mockEvent := &nitrolite.CustodyCreated{
diff --git a/clearnode/docs/API.md b/clearnode/docs/API.md
index 8c232f9ab..d05db9624 100644
--- a/clearnode/docs/API.md
+++ b/clearnode/docs/API.md
@@ -830,9 +830,9 @@ In the request, the user must specify funds destination. After the channel is cl
{
"res": [1, "close_channel", [{
"channel_id": "0x4567890123abcdef...",
- "intent": 3, // IntentFINALIZE - constant magic number for closing channel
+ "intent": 3, // IntentFINALIZE - constant specifying that this is a final state
"version": 123,
- "state_data": "0x0000000000000000000000000000000000000000000000000000000000001ec7",
+ "state_data": "0xdeadbeef",
"allocations": [
{
"destination": "0x1234567890abcdef...", // Provided funds address
@@ -845,12 +845,7 @@ In the request, the user must specify funds destination. After the channel is cl
"amount": "50000"
}
],
- "state_hash": "0xLedgerStateHash",
- "server_signature": {
- "v": "27",
- "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- "s": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
- }
+ "server_signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1c",
}], 1619123456789],
"sig": ["0xabcd1234..."]
}
@@ -890,7 +885,7 @@ Example:
{
"res": [1, "resize_channel", [{
"channel_id": "0x4567890123abcdef...",
- "state_data": "0x0000000000000000000000000000000000000000000000000000000000002ec7",
+ "state_data": "0xdeadbeef",
"intent": 2, // IntentRESIZE
"version": 5,
"allocations": [
@@ -905,12 +900,7 @@ Example:
"amount": "0"
}
],
- "state_hash": "0xLedgerStateHash",
- "server_signature": {
- "v": "28",
- "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
- "s": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
- }
+ "server_signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1c",
}], 1619123456789],
"sig": ["0xabcd1234..."]
}
diff --git a/clearnode/nitrolite/bindings.go b/clearnode/nitrolite/bindings.go
index 3ed00fb19..e45cc2480 100644
--- a/clearnode/nitrolite/bindings.go
+++ b/clearnode/nitrolite/bindings.go
@@ -44,26 +44,19 @@ type Channel struct {
Nonce uint64
}
-// Signature is an auto generated low-level Go binding around an user-defined struct.
-type Signature struct {
- V uint8
- R [32]byte
- S [32]byte
-}
-
// State is an auto generated low-level Go binding around an user-defined struct.
type State struct {
Intent uint8
Version *big.Int
Data []byte
Allocations []Allocation
- Sigs []Signature
+ Sigs [][]byte
}
// CustodyMetaData contains all meta data concerning the Custody contract.
var CustodyMetaData = &bind.MetaData{
- ABI: "[{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState[]\",\"name\":\"proofs\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature\",\"name\":\"challengerSig\",\"type\":\"tuple\"}],\"name\":\"challenge\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState[]\",\"name\":\"proofs\",\"type\":\"tuple[]\"}],\"name\":\"checkpoint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"close\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structChannel\",\"name\":\"ch\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"initial\",\"type\":\"tuple\"}],\"name\":\"create\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structChannel\",\"name\":\"ch\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"initial\",\"type\":\"tuple\"}],\"name\":\"depositAndCreate\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"}],\"name\":\"getAccountsBalances\",\"outputs\":[{\"internalType\":\"uint256[][]\",\"name\":\"\",\"type\":\"uint256[][]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"}],\"name\":\"getChannelBalances\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"balances\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"name\":\"getChannelData\",\"outputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structChannel\",\"name\":\"channel\",\"type\":\"tuple\"},{\"internalType\":\"enumChannelStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"address[]\",\"name\":\"wallets\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"challengeExpiry\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"lastValidState\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"}],\"name\":\"getOpenChannels\",\"outputs\":[{\"internalType\":\"bytes32[][]\",\"name\":\"\",\"type\":\"bytes32[][]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature\",\"name\":\"sig\",\"type\":\"tuple\"}],\"name\":\"join\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"internalType\":\"structState[]\",\"name\":\"proofs\",\"type\":\"tuple[]\"}],\"name\":\"resize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"state\",\"type\":\"tuple\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"Challenged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"state\",\"type\":\"tuple\"}],\"name\":\"Checkpointed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"finalState\",\"type\":\"tuple\"}],\"name\":\"Closed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"wallet\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structChannel\",\"name\":\"channel\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"structSignature[]\",\"name\":\"sigs\",\"type\":\"tuple[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"initial\",\"type\":\"tuple\"}],\"name\":\"Created\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"wallet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"Joined\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"name\":\"Opened\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"int256[]\",\"name\":\"deltaAllocations\",\"type\":\"int256[]\"}],\"name\":\"Resized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"wallet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawn\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"ChallengeNotExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ChannelNotFinal\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"name\":\"ChannelNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DepositAlreadyFulfilled\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expectedFulfilled\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actualFulfilled\",\"type\":\"uint256\"}],\"name\":\"DepositsNotFulfilled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"available\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"required\",\"type\":\"uint256\"}],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAdjudicator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAllocations\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAmount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidChallengePeriod\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidChallengerSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidParticipant\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidState\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidStateSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidStatus\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidValue\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TransferFailed\",\"type\":\"error\"}]",
- Bin: "0x0x608080604052346015576149a0908161001a8239f35b5f80fdfe60806040526004361015610011575f80fd5b5f3560e01c8063259311c9146118b05780632f33c4d6146116f75780635a9eb80e146116275780638340f549146115f2578063925bc4791461157c578063a22b823d14611054578063bc7b456f14610fc7578063d0cce1e814610b9f578063d37ff7b514610b33578063d710e92f146109a5578063de22731f146103a1578063e617208c1461023b5763f3fef3a3146100a8575f80fd5b34610237576040600319360112610237576100c161211f565b60243590335f52600160205260405f20906001600160a01b0381165f528160205260405f205491838310610207576001600160a01b0392508282165f5260205260405f20610110848254612691565b90551690816101b5575f80808084335af13d156101b0573d61013181612313565b9061013f60405192836120e4565b81525f60203d92013e5b1561017d575b6040519081527fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb60203392a3005b907fbf182be8000000000000000000000000000000000000000000000000000000005f526004523360245260445260645ffd5b610149565b6102026040517fa9059cbb000000000000000000000000000000000000000000000000000000006020820152336024820152826044820152604481526101fc6064826120e4565b83614666565b61014f565b50507fcf479181000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5f80fd5b34610237576020600319360112610237575f606060405161025b81612090565b818152826020820152826040820152015261027461269e565b506004355f525f60205260405f2061028b81612602565b60ff60038301541691604051906102a36060836120e4565b600282526040366020840137600481015f5b600281106103735750506102d0600f600e8301549201612bdb565b6040519460a0865267ffffffffffffffff60606102fa8751608060a08b01526101208a01906121ae565b966001600160a01b0360208201511660c08a01528260408201511660e08a0152015116610100870152600581101561035f57859461035b9461034892602088015286820360408801526121ae565b9160608501528382036080850152612219565b0390f35b634e487b7160e01b5f52602160045260245ffd5b806001600160a01b0361038860019385612dcf565b90549060031b1c1661039a82876126f9565b52016102b5565b34610237576103af3661201d565b5050815f525f60205260405f2060ff600382015416600581101561035f57801561099257600281036106c8575081356004811015610237576103f0816121ea565b600381036106b957602083013580156106b95760808401916002610414848761258c565b9050036106915761043761042785612602565b6104313688612439565b90613fdc565b156106915761044990600f85016127ef565b60108301556011820161045f604085018561265e565b9067ffffffffffffffff821161067d576104838261047d8554612808565b85612856565b5f90601f8311600114610619576104b192915f918361060e575b50508160011b915f199060031b1c19161790565b90555b601282016104c5606085018561258c565b91906104d1838361289b565b905f5260205f205f915b8383106105a857505050506104f460138301918461258c565b906104ff828461291d565b915f5260205f205f925b82841061056857505050507f3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c9109161054f6105496012610563945b01612b5a565b85614283565b604051918291602083526020830190612e13565b0390a2005b80359060ff8216820361023757606060039160ff6001941660ff198654161785556020810135848601556040810135600286015501920193019290610509565b60036060826001600160a01b036105c0600195612909565b166001600160a01b03198654161785556105dc60208201612909565b6001600160a01b0385870191166001600160a01b031982541617905560408101356002860155019201920191906104db565b01359050888061049d565b601f19831691845f5260205f20925f5b818110610665575090846001959493921061064c575b505050811b0190556104b4565b01355f19600384901b60f8161c1916905587808061063f565b91936020600181928787013581550195019201610629565b634e487b7160e01b5f52604160045260245ffd5b7f773a750f000000000000000000000000000000000000000000000000000000005f5260045ffd5b63baf3f0f760e01b5f5260045ffd5b60030361096a57600e810180544210156109365782356004811015610237576106f0816121ea565b600381036106b95761070e61070484612602565b6104313687612439565b15610691575f6107229255600f83016127ef565b602082013560108201556011810161073d604084018461265e565b9067ffffffffffffffff821161067d5761075b8261047d8554612808565b5f90601f83116001146108d25761078892915f91836108c75750508160011b915f199060031b1c19161790565b90555b6012810161079c606084018461258c565b91906107a8838361289b565b905f5260205f205f915b8383106108615750505050601381016107ce608084018461258c565b906107d9828461291d565b915f5260205f205f925b82841061082157505050507f3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c9109161054f610549601261056394610543565b80359060ff8216820361023757606060039160ff6001941660ff1986541617855560208101358486015560408101356002860155019201930192906107e3565b60036060826001600160a01b03610879600195612909565b166001600160a01b031986541617855561089560208201612909565b6001600160a01b0385870191166001600160a01b031982541617905560408101356002860155019201920191906107b2565b01359050878061049d565b601f19831691845f5260205f20925f5b81811061091e5750908460019594939210610905575b505050811b01905561078b565b01355f19600384901b60f8161c191690558680806108f8565b919360206001819287870135815501950192016108e2565b507f3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c9109161054f610549601261056394610543565b7ff525e320000000000000000000000000000000000000000000000000000000005f5260045ffd5b836379c1d89f60e11b5f5260045260245ffd5b346102375760206003193601126102375760043567ffffffffffffffff8111610237576109d6903690600401612149565b8051906109fb6109e583612107565b926109f360405194856120e4565b808452612107565b90610a0e601f196020850193018361298b565b5f5b8151811015610aa2576001600160a01b03610a2b82846126f9565b51165f526001602052600160405f20016040519081602082549182815201915f5260205f20905f905b808210610a8a5750505090610a6e816001949303826120e4565b610a7882876126f9565b52610a8381866126f9565b5001610a10565b90919260016020819286548152019401920190610a54565b50509060405191829160208301906020845251809152604083019060408160051b85010192915f905b828210610ada57505050500390f35b9193909294603f19908203018252845190602080835192838152019201905f905b808210610b1b575050506020806001929601920192018594939192610acb565b90919260208060019286518152019401920190610afb565b346102375760406003193601126102375760043567ffffffffffffffff811161023757608060031982360301126102375760243567ffffffffffffffff81116102375760a0600319823603011261023757602091610b9791600401906004016137da565b604051908152f35b3461023757610bad3661201d565b835f525f60205260405f2091600383019160ff83541690600582101561035f578115610fb4576004821461096a57853592600484101561023757610bf0846121ea565b836106b957600f86019260ff84541690600181145f14610c32577ff525e320000000000000000000000000000000000000000000000000000000005f5260045ffd5b600203610f0157506001600160a01b0360018701541691610c66610c56368a612439565b610c5f86612bdb565b9085614482565b156106b957610c90926020926040518095819482936305b959ef60e01b84528d8d60048601612f8f565b03915afa908115610ef6575f91610ec7575b50156106b957610cbd925b600260ff198254161790556127ef565b6020820135601082015560118101610cd8604084018461265e565b9067ffffffffffffffff821161067d57610cf68261047d8554612808565b5f90601f8311600114610e6357610d2392915f91836108c75750508160011b915f199060031b1c19161790565b90555b60128101610d37606084018461258c565b9190610d43838361289b565b905f5260205f205f915b838310610dfd57868660138701610d67608083018361258c565b90610d72828461291d565b915f5260205f205f925b828410610dbd57857fa876bb57c3d3b4b0363570fd7443e30dfe18d4b422fe9898358262d78485325d61056387604051918291602083526020830190612e13565b80359060ff8216820361023757606060039160ff6001941660ff198654161785556020810135848601556040810135600286015501920193019290610d7c565b60036060826001600160a01b03610e15600195612909565b166001600160a01b0319865416178555610e3160208201612909565b6001600160a01b0385870191166001600160a01b03198254161790556040810135600286015501920192019190610d4d565b601f19831691845f5260205f20925f5b818110610eaf5750908460019594939210610e96575b505050811b019055610d26565b01355f19600384901b60f8161c19169055868080610e89565b91936020600181928787013581550195019201610e73565b610ee9915060203d602011610eef575b610ee181836120e4565b81019061270d565b87610ca2565b503d610ed7565b6040513d5f823e3d90fd5b90916020610f35916001600160a01b0360018a0154169460405193849283926305b959ef60e01b84528d8d60048601612f8f565b0381865afa908115610ef6575f91610f95575b50156106b957610f57816121ea565b15610f6d575b50610cbd925f600e860155610cad565b610f8a90610f7b3688612439565b610f8484612bdb565b91614482565b156106b95786610f5d565b610fae915060203d602011610eef57610ee181836120e4565b89610f48565b866379c1d89f60e11b5f5260045260245ffd5b346102375760c06003193601126102375760243567ffffffffffffffff81116102375760a060031982360301126102375760443567ffffffffffffffff811161023757611018903690600401611fec565b9060607fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c3601126102375761105292600401600435613027565b005b346102375760a060031936011261023757600435606060431936011261023757805f525f60205260405f2090600382019160ff835416600581101561035f578015611569575f190161096a5760016024350361154157600d81015461151957600f8101906110d36110c482612602565b6110cd84612bdb565b9061406d565b6001600160a01b03611100816110e885612d3a565b90549060031b1c16926110fa366123c5565b906146d3565b16036106915761110f82612bdb565b936040519461111f6060876120e4565b6002865260405f5b8181106114f0575050608081019561113f87516126c8565b51611149826126c8565b52611153816126c8565b5061115d366123c5565b611166826126e9565b52611170816126e9565b50865260018301546001600160a01b031660405190602061119181846120e4565b5f8352601f19015f5b8181106114d95750506111c79160209160405180809581946305b959ef60e01b8352888b6004850161276c565b03915afa908115610ef6575f916114ba575b50156106b9576112456111ee60088501612d67565b9461122086600c87019060206001916001600160a01b0380825116166001600160a01b03198554161784550151910155565b6005850180546001600160a01b03191633179055825190611240826121ea565b6127ef565b6020810151601084015560118301604082015180519067ffffffffffffffff821161067d576112788261047d8554612808565b602090601f8311600114611457576112a692915f918361144c5750508160011b915f199060031b1c19161790565b90555b6060601284019101519060208251926112c2848461289b565b01905f5260205f205f915b8383106113e9575050505060138201945160208151916112ed838961291d565b01955f5260205f20955f905b8282106113b157835460ff19166002178455602087611359886001600160a01b036113238a612d3a565b90549060031b1c165f526001845261134183600160405f20016146fc565b50836001600160a01b03825116910151908333614154565b807fe8e915db7b3549b9e9e9b3e2ec2dc3edd1f76961504366998824836401f6846a8360405160018152a260405190807fd087f17acc177540af5f382bc30c65363705b90855144d285a822536ee11fdd15f80a28152f35b60036020826040600194518c60ff1960ff8084511616915416178d558c8685830151910155015160028c0155019801910190966112f9565b60036020826040600194516001600160a01b0380825116166001600160a01b03198854161787556001600160a01b0384820151166001600160a01b0387890191166001600160a01b031982541617905501516002860155019201920191906112cd565b015190508a8061049d565b90601f19831691845f52815f20925f5b8181106114a2575090846001959493921061148a575b505050811b0190556112a9565b01515f1960f88460031b161c1916905589808061147d565b92936020600181928786015181550195019301611467565b6114d3915060203d602011610eef57610ee181836120e4565b876111d9565b6020906114e461269e565b8282870101520161119a565b6020906040516114ff81612074565b5f81525f838201525f604082015282828b01015201611127565b7f1b136079000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa145c43e000000000000000000000000000000000000000000000000000000005f5260045ffd5b826379c1d89f60e11b5f5260045260245ffd5b60806003193601126102375761159061211f565b6044359067ffffffffffffffff82116102375760806003198336030112610237576064359167ffffffffffffffff83116102375760a06003198436030112610237576020926115e6610b979360243590336129e8565b600401906004016137da565b60606003193601126102375761160661211f565b602435906001600160a01b03821682036102375761105291604435916129e8565b346102375760406003193601126102375760043560243567ffffffffffffffff81116102375761165b903690600401612149565b61166581516129a7565b5f5b82518110156116b257600190845f525f602052601460405f20016001600160a01b038061169484886126f9565b5116165f5260205260405f20546116ab82856126f9565b5201611667565b506040518091602082016020835281518091526020604084019201905f5b8181106116de575050500390f35b82518452859450602093840193909201916001016116d0565b346102375760406003193601126102375760043567ffffffffffffffff811161023757611728903690600401611fec565b60243567ffffffffffffffff811161023757611748903690600401611fec565b91909261175482612107565b9361176260405195866120e4565b82855261176e83612107565b93611781601f196020880196018661298b565b5f5b84811061181a57858760405191829160208301906020845251809152604083019060408160051b85010192915f905b8282106117c157505050500390f35b9193909294603f19908203018252845190602080835192838152019201905f905b8082106118025750505060208060019296019201920185949391926117b2565b909192602080600192865181520194019201906117e2565b611823826129a7565b61182d82896126f9565b5261183881886126f9565b505f5b82811061184b5750600101611783565b6001906001600160a01b03611869611864858a8a6129d8565b612909565b165f528160205260405f206001600160a01b0361188a61186484888a6129d8565b165f5260205260405f20546118a9826118a3868d6126f9565b516126f9565b520161183b565b34610237576118be3661201d565b9190835f525f60205260405f209360ff600386015416600581101561035f578015611fd9577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0161096a5783156106b957823592600484101561023757611924846121ea565b600284036106b957823593609e198436030194858112156102375761194c9085013690612439565b966020830135602089015160018101809111611b405781036106b95760608901936119778551613fa6565b606081019161199861199361198c858561258c565b369161232f565b613fa6565b6119ae6119a485612602565b6104313685612439565b15610691576119c0604083018361265e565b810195906020818803126102375780359067ffffffffffffffff821161023757019686601f89011215610237578735966119f988612107565b98611a076040519a8b6120e4565b888a5260208a01906020829a60051b82010192831161023757602001905b828210611fc95750505051611a3d61198c868661258c565b9060028951036106b957611a6f816040611a6681611a5d611aa6966126c8565b510151926126e9565b5101519061257f565b91611aa0611a90611a7f8c6126c8565b51611a898d6126e9565b5190614052565b916040611a6681611a5d846126c8565b92614052565b03611fa1575f198b019b8b8d11611b4057611ac08d612107565b9c806040519e8f90611ad290826120e4565b52611adc90612107565b601f19018d5f5b828110611f885750505060015b8c811015611b54578060051b8b01358c811215610237575f19820191908c01818311611b40578f92600193611b29611b39933690612439565b611b3383836126f9565b526126f9565b5001611af0565b634e487b7160e01b5f52601160045260245ffd5b5060208d611b88926001600160a01b0360018a015416906040518095819482936305b959ef60e01b84528d6004850161276c565b03915afa908115610ef6575f91611f69575b50156106b957611bad90600f86016127ef565b601084015560118301611bc3604083018361265e565b9067ffffffffffffffff821161067d57611be18261047d8554612808565b5f90601f8311600114611f0557611c0e92915f9183611efa5750508160011b915f199060031b1c19161790565b90555b60128301611c1f838361258c565b9190611c2b838361289b565b905f5260205f205f915b838310611e94575050505060138301611c51608083018361258c565b90611c5c828461291d565b915f5260205f205f925b828410611e545750505050611c7e9161198c9161258c565b9060068101916001600160a01b038354165f5b60028110611dfb57505f5b60028110611d5b575050600a5f9201915b60028110611d18575050505060405191602083019060208452518091526040830191905f5b818110611d0257857ff3b6c524f73df7344d9fcf2f960a57aba7fba7e292d8b79ed03d786f7b2b112f86860387a2005b8251845260209384019390920191600101611cd2565b806040611d27600193856126f9565b51015182611d358388612b47565b5001556040611d4482856126f9565b51015182611d528387612b47565b50015501611cad565b5f611d6982899796976126f9565b5112611d7b575b600101939293611c9c565b6001600160a01b03611d908260048801612dcf565b90549060031b1c1690611da381896126f9565b517f80000000000000000000000000000000000000000000000000000000000000008114611b4057600192611df49160405191611ddf83612074565b82528560208301525f0360408201528a6145b3565b9050611d70565b805f611e0c6001938a9897986126f9565b5113611e1c575b01939293611c91565b611e4f6001600160a01b03611e348360048a01612dcf565b90549060031b1c16848b611e48858d6126f9565b5192614154565b611e13565b80359060ff8216820361023757606060039160ff6001941660ff198654161785556020810135848601556040810135600286015501920193019290611c66565b60036060826001600160a01b03611eac600195612909565b166001600160a01b0319865416178555611ec860208201612909565b6001600160a01b0385870191166001600160a01b03198254161790556040810135600286015501920192019190611c35565b013590508a8061049d565b601f19831691845f5260205f20925f5b818110611f515750908460019594939210611f38575b505050811b019055611c11565b01355f19600384901b60f8161c19169055898080611f2b565b91936020600181928787013581550195019201611f15565b611f82915060203d602011610eef57610ee181836120e4565b89611b9a565b6020918282611f9561269e565b92010152018e90611ae3565b7f52e4cb1c000000000000000000000000000000000000000000000000000000005f5260045ffd5b8135815260209182019101611a25565b506379c1d89f60e11b5f5260045260245ffd5b9181601f840112156102375782359167ffffffffffffffff8311610237576020808501948460051b01011161023757565b6060600319820112610237576004359160243567ffffffffffffffff81116102375760a0600319828503011261023757600401916044359067ffffffffffffffff82116102375761207091600401611fec565b9091565b6060810190811067ffffffffffffffff82111761067d57604052565b6080810190811067ffffffffffffffff82111761067d57604052565b60a0810190811067ffffffffffffffff82111761067d57604052565b6040810190811067ffffffffffffffff82111761067d57604052565b90601f601f19910116810190811067ffffffffffffffff82111761067d57604052565b67ffffffffffffffff811161067d5760051b60200190565b600435906001600160a01b038216820361023757565b35906001600160a01b038216820361023757565b9080601f8301121561023757813561216081612107565b9261216e60405194856120e4565b81845260208085019260051b82010192831161023757602001905b8282106121965750505090565b602080916121a384612135565b815201910190612189565b90602080835192838152019201905f5b8181106121cb5750505090565b82516001600160a01b03168452602093840193909201916001016121be565b6004111561035f57565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b8051612224816121ea565b825260208101516020830152612249604082015160a0604085015260a08401906121f4565b906060810151918381036060850152602080845192838152019301905f5b8181106122cb5750505060800151916080818303910152602080835192838152019201905f5b81811061229a5750505090565b909192602060606001926040875160ff8151168352848101518584015201516040820152019401910191909161228d565b909193602061230960019287519060406060926001600160a01b0381511683526001600160a01b036020820151166020840152015160408201520190565b9501929101612267565b67ffffffffffffffff811161067d57601f01601f191660200190565b92919261233b82612107565b9361234960405195866120e4565b606060208685815201930282019181831161023757925b82841061236d5750505050565b60608483031261023757602060609160405161238881612074565b61239187612135565b815261239e838801612135565b8382015260408701356040820152815201930192612360565b359060ff8216820361023757565b604319606091011261023757604051906123de82612074565b8160443560ff8116810361023757815260643560208201526040608435910152565b91908260609103126102375760405161241881612074565b6040808294612426816123b7565b8452602081013560208501520135910152565b919060a08382031261023757604051612451816120ac565b80938035600481101561023757825260208101356020830152604081013567ffffffffffffffff811161023757810183601f8201121561023757803561249681612313565b916124a460405193846120e4565b818352856020838301011161023757815f92602080930183860137830101526040830152606081013567ffffffffffffffff811161023757810183601f8201121561023757838160206124f99335910161232f565b606083015260808101359067ffffffffffffffff8211610237570182601f8201121561023757803561252a81612107565b9361253860405195866120e4565b8185526020606081870193028401019281841161023757602001915b838310612565575050505060800152565b60206060916125748486612400565b815201920191612554565b91908201809211611b4057565b903590601e1981360301821215610237570180359067ffffffffffffffff82116102375760200191606082023603831361023757565b90602082549182815201915f5260205f20905f5b8181106125e35750505090565b82546001600160a01b03168452602090930192600192830192016125d6565b9060405161260f81612090565b606067ffffffffffffffff600283956040516126368161262f81856125c2565b03826120e4565b85528260018201546001600160a01b038116602088015260a01c166040860152015416910152565b903590601e1981360301821215610237570180359067ffffffffffffffff82116102375760200191813603831361023757565b91908203918211611b4057565b604051906126ab826120ac565b60606080835f81525f602082015282604082015282808201520152565b8051156126d55760200190565b634e487b7160e01b5f52603260045260245ffd5b8051600110156126d55760400190565b80518210156126d55760209160051b010190565b90816020910312610237575180151581036102375790565b9060808152606067ffffffffffffffff600261274460808501866125c2565b948260018201546001600160a01b038116602088015260a01c16604086015201541691015290565b9161278261279092606085526060850190612725565b908382036020850152612219565b906040818303910152815180825260208201916020808360051b8301019401925f915b8383106127c257505050505090565b90919293946020806127e083601f1986600196030187528951612219565b970193019301919392906127b3565b906127f9816121ea565b60ff60ff198354169116179055565b90600182811c92168015612836575b602083101461282257565b634e487b7160e01b5f52602260045260245ffd5b91607f1691612817565b81811061284b575050565b5f8155600101612840565b9190601f811161286557505050565b61288f925f5260205f20906020601f840160051c83019310612891575b601f0160051c0190612840565b565b9091508190612882565b9068010000000000000000811161067d578154918181558282106128be57505050565b82600302926003840403611b405781600302916003830403611b40575f5260205f2091820191015b8181106128f1575050565b805f600392555f60018201555f6002820155016128e6565b356001600160a01b03811681036102375790565b9068010000000000000000811161067d5781549181815582821061294057505050565b82600302926003840403611b405781600302916003830403611b40575f5260205f2091820191015b818110612973575050565b805f600392555f60018201555f600282015501612968565b5f5b82811061299957505050565b60608282015260200161298d565b906129b182612107565b6129be60405191826120e4565b828152601f196129ce8294612107565b0190602036910137565b91908110156126d55760051b0190565b908215612b1f576001600160a01b0316918215918215612ae857813403612ac0577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7916001600160a01b036020925b1693845f526001835260405f20865f52835260405f20612a5883825461257f565b905515612a69575b604051908152a3565b612abb6040517f23b872dd000000000000000000000000000000000000000000000000000000008482015233602482015230604482015282606482015260648152612ab56084826120e4565b86614666565b612a60565b7faa7feadc000000000000000000000000000000000000000000000000000000005f5260045ffd5b34612ac0577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7916001600160a01b03602092612a37565b7f2c5211c6000000000000000000000000000000000000000000000000000000005f5260045ffd5b9060028110156126d55760011b01905f90565b908154612b6681612107565b92612b7460405194856120e4565b81845260208401905f5260205f205f915b838310612b925750505050565b60036020600192604051612ba581612074565b6001600160a01b0386541681526001600160a01b0385870154168382015260028601546040820152815201920192019190612b85565b90604051612be8816120ac565b809260ff815416612bf8816121ea565b8252600181015460208301526040516002820180545f91612c1882612808565b8085529160018116908115612d135750600114612ccf575b505090612c42816004949303826120e4565b6040840152612c5360038201612b5a565b606084015201908154612c6581612107565b92612c7360405194856120e4565b81845260208401905f5260205f205f915b838310612c95575050505060800152565b60036020600192604051612ca881612074565b60ff8654168152848601548382015260028601546040820152815201920192019190612c84565b5f9081526020812094939250905b808210612cf75750919250908101602001612c4282612c30565b9192936001816020925483858801015201910190939291612cdd565b60ff191660208087019190915292151560051b85019092019250612c429150839050612c30565b8054600110156126d5575f52600160205f2001905f90565b80548210156126d5575f5260205f2001905f90565b90604051612d74816120c8565b6020600182946001600160a01b0381541684520154910152565b9190612dbc576020816001600160a01b03806001945116166001600160a01b03198554161784550151910155565b634e487b7160e01b5f525f60045260245ffd5b60028210156126d55701905f90565b9035601e198236030181121561023757016020813591019167ffffffffffffffff821161023757606082023603831361023757565b8035600481101561023757612e27816121ea565b8252602081013560208301526040810135601e198236030181121561023757810160208135910167ffffffffffffffff82116102375781360381136102375781601f1992601f9260a060408801528160a088015260c08701375f60c08287010152011682019060c082019160e0612ea16060840184612dde565b86840360c0016060880152948590529101925f5b818110612f2757505050612ecf8160806020930190612dde565b92909360808183039101528281520191905f5b818110612eef5750505090565b90919260608060019260ff612f03886123b7565b16815260208701356020820152604087013560408201520194019101919091612ee2565b9091936060806001926001600160a01b03612f4189612135565b1681526001600160a01b03612f5860208a01612135565b16602082015260408881013590820152019501929101612eb5565b929190612f8a602091604086526040860190612e13565b930152565b91612fa5612fb392606085526060850190612725565b908382036020850152612e13565b906040818303910152828152602081019260208160051b83010193835f91609e1982360301945b848410612feb575050505050505090565b90919293949596601f1982820301835287358781121561023757602061301660019387839401612e13565b990193019401929195949390612fda565b91939290825f525f60205260405f20600381019560ff87541690600582101561035f5781156136e6575f946003831480156136d9575b61096a578435916004831015968761023757613078846121ea565b600384146106b95761309661308c87612602565b6110cd368a612439565b6040516130a2816120c8565b8754156126d557875f526001600160a01b0360205f20541681526001600160a01b0380613142816130d28c612d3a565b90549060031b1c1694602085019586526130ed366064612400565b9060405160208101918252604080820152600960608201527f6368616c6c656e6765000000000000000000000000000000000000000000000060808201526080815261313a60a0826120e4565b5190206146d3565b92511691169081141591826136c4575b505061369c57600f86019460ff8654169161035f576001146135f5578790613179816121ea565b600181036134a257506102375761318f836121ea565b6001830361340e5750506131b56131a63686612439565b6131af84612bdb565b9061420e565b156106b9575b6131d667ffffffffffffffff600185015460a01c164261257f565b9485600e850155610237576131ea916127ef565b6020820135601082015560118101613205604084018461265e565b9067ffffffffffffffff821161067d576132238261047d8554612808565b5f90601f83116001146133aa5761325092915f918361339f5750508160011b915f199060031b1c19161790565b90555b60128101613264606084018461258c565b9190613270838361289b565b905f5260205f205f915b838310613339575050505060130194613296608083018361258c565b906132a1828961291d565b965f5260205f205f975b8289106132f9575050507f2cce3a04acfb5f7911860de30611c13af2df5880b4a1f829fa7b4f2a26d0375693949550600360ff198254161790556132f460405192839283612f73565b0390a2565b80359060ff8216820361023757606060039160ff6001941660ff1986541617855560208101358486015560408101356002860155019201980197906132ab565b60036060826001600160a01b03613351600195612909565b166001600160a01b031986541617855561336d60208201612909565b6001600160a01b0385870191166001600160a01b0319825416179055604081013560028601550192019201919061327a565b013590505f8061049d565b601f19831691845f5260205f20925f5b8181106133f657509084600195949392106133dd575b505050811b019055613253565b01355f19600384901b60f8161c191690555f80806133d0565b919360206001819287870135815501950192016133ba565b6001600160a01b036001860154169161343361342a3689612439565b610c5f87612bdb565b156106b95761345d926020926040518095819482936305b959ef60e01b84528c8c60048601612f8f565b03915afa908115610ef6575f91613483575b506131bb5763baf3f0f760e01b5f5260045ffd5b61349c915060203d602011610eef57610ee181836120e4565b5f61346f565b90506134ad816121ea565b8061356e575086610237576134c1836121ea565b826106b9576134dc6134d33688612439565b6131af86612bdb565b156134ea575b50505b6131bb565b6001600160a01b036001860154169161350661342a3689612439565b156106b957613530926020926040518095819482936305b959ef60e01b84528c8c60048601612f8f565b03915afa908115610ef6575f9161354f575b50156106b9575f806134e2565b613568915060203d602011610eef57610ee181836120e4565b5f613542565b600291975061357c816121ea565b036106b95761358a826121ea565b600182146106b9575f9561359d836121ea565b826135be576001600160a01b036001860154169161343361342a3689612439565b505093505f936135cd816121ea565b600281036106b9576135e26131a63686612439565b6134e55763baf3f0f760e01b5f5260045ffd5b5050505091939495505061361791506131af6136113685612439565b91612bdb565b156106b9576132f48161365461054961198c60607f3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c91096018461258c565b837f2cce3a04acfb5f7911860de30611c13af2df5880b4a1f829fa7b4f2a26d0375660405180613685428683612f73565b0390a2604051918291602083526020830190612e13565b7f61a44f6e000000000000000000000000000000000000000000000000000000005f5260045ffd5b516001600160a01b0316141590505f80613152565b505f95506004831461305d565b856379c1d89f60e11b5f5260045260245ffd5b903590601e1981360301821215610237570180359067ffffffffffffffff821161023757602001918160051b3603831361023757565b3567ffffffffffffffff811681036102375790565b359067ffffffffffffffff8216820361023757565b919091608081840312610237576040519061377382612090565b819381359067ffffffffffffffff8211610237578261379b606094926137c594869401612149565b85526137a960208201612135565b60208601526137ba60408201613744565b604086015201613744565b910152565b91908110156126d5576060020190565b9060026137e783806136f9565b905014801590613f7e575b8015613f50575b8015613f07575b61154157602082016001600160a01b0361381982612909565b1615613edf576040830192610e1067ffffffffffffffff6138398661372f565b1610613eb7578235600481101561023757613853816121ea565b600181036106b957602084013594856106b9576138786138733685613759565b614547565b95865f525f60205260ff600360405f20015416600581101561035f5761096a576138af6138a53686613759565b6110cd3689612439565b90608087019160016138c1848a61258c565b905003610691576138d2838961258c565b919091156126d5576138e487806136f9565b919091156126d55761390e6001600160a01b0392916110fa6139068594612909565b953690612400565b92169116036106915760608701916002613928848a61258c565b905003611fa157885f525f60205260405f209161394587806136f9565b9067ffffffffffffffff821161067d5768010000000000000000821161067d578454828655808310613e9b575b50845f5260205f205f5b838110613e805750505050600183016001600160a01b0361399c8a612909565b166001600160a01b03198254161781556139b58661372f565b7fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffff000000000000000000000000000000000000000083549260a01b169116179055613a736002840196606089019767ffffffffffffffff613a1e8a61372f565b82547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016911617905560038501805460ff191660011790556004850180546001600160a01b03191633179055600f85016127ef565b601083015560118201613a8960408a018a61265e565b9067ffffffffffffffff821161067d57613aa78261047d8554612808565b5f90601f8311600114613e1c57613ad492915f918361339f5750508160011b915f199060031b1c19161790565b90555b60128201613ae5848a61258c565b9190613af1838361289b565b905f5260205f205f915b838310613db65750505050613b1460138301918961258c565b90613b1f828461291d565b915f5260205f205f925b828410613d7657505050505f91600a600683019201925b60028110613ce9575050613b56613b8191612d67565b80929060206001916001600160a01b0380825116166001600160a01b03198554161784550151910155565b613b8b84806136f9565b919091156126d5576001600160a01b03613ba7613bd893612909565b165f526001602052613bbf88600160405f20016146fc565b5060206001600160a01b03825116910151908833614154565b604051936040855260c08501938035601e198236030181121561023757016020813591019467ffffffffffffffff8211610237578160051b36038613610237576080604088015281905260e0860195889590949392915f5b818110613cb5575050509467ffffffffffffffff613c9a859482613c8f613caf966001600160a01b03613c847f7044488f9b947dc40d596a71992214b1050317a18ab1dced28e9d22320c398429c9d612135565b1660608a0152613744565b166080870152613744565b1660a084015282810360208401523396612e13565b0390a390565b91965091929394966020806001926001600160a01b03613cd48b612135565b16815201970191019189969795949392613c30565b80613d4e8a6040613d1f84613d0d88613d196020613d1360019b613d0d858b61258c565b906137ca565b01612909565b9561258c565b01356001600160a01b0360405192613d36846120c8565b1682526020820152613d488387612b47565b90612d8e565b613d70604051613d5d816120c8565b5f81525f6020820152613d488388612b47565b01613b40565b80359060ff8216820361023757606060039160ff6001941660ff198654161785556020810135848601556040810135600286015501920193019290613b29565b60036060826001600160a01b03613dce600195612909565b166001600160a01b0319865416178555613dea60208201612909565b6001600160a01b0385870191166001600160a01b03198254161790556040810135600286015501920192019190613afb565b601f19831691845f5260205f20925f5b818110613e685750908460019594939210613e4f575b505050811b019055613ad7565b01355f19600384901b60f8161c191690555f8080613e42565b91936020600181928787013581550195019201613e2c565b6001906020613e8e85612909565b940193818401550161397c565b613eb190865f528360205f209182019101612840565b5f613972565b7fb4e12433000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fea9e70ce000000000000000000000000000000000000000000000000000000005f5260045ffd5b50613f1282806136f9565b156126d557613f2090612909565b613f2a83806136f9565b600110156126d5576001600160a01b03613f476020829301612909565b16911614613800565b50613f5b82806136f9565b600110156126d557613f7760206001600160a01b039201612909565b16156137f9565b50613f8982806136f9565b156126d557613f9f6001600160a01b0391612909565b16156137f2565b60028151036106b9576001600160a01b036020613fd18282613fc7866126c8565b51015116936126e9565b51015116036106b957565b906080613fe9828461406d565b91019160028351510361404b575f5b600281106140095750505050600190565b6140148185516126f9565b516001600160a01b036140358161402c8587516126f9565b511692866146d3565b160361404357600101613ff8565b505050505f90565b5050505f90565b9190915f8382019384129112908015821691151617611b4057565b61407690614547565b90805190614083826121ea565b6020810151916140c86060604084015193015192604051948593602085019788526140ad816121ea565b6040850152606084015260a0608084015260c08301906121f4565b91601f198284030160a0830152602080825194858152019101925f5b818110614106575050614100925003601f1981018352826120e4565b51902090565b91600191935061414560209186519060406060926001600160a01b0381511683526001600160a01b036020820151166020840152015160408201520190565b940191019184929391936140e4565b8315614208576001600160a01b03165f52600160205260405f206001600160a01b0383165f528060205260405f20548481106141d8578461419491612691565b906001600160a01b0384165f5260205260405f20555f525f6020526001600160a01b03601460405f200191165f526020526141d460405f2091825461257f565b9055565b84907fcf479181000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b50505050565b6040516142398161422b6020820194602086526040830190612219565b03601f1981018352826120e4565b5190209060405161425a8161422b6020820194602086526040830190612219565b5190201490565b60048101905b818110614272575050565b5f8082556001820155600201614267565b90815f525f60205260405f209060038201600460ff1982541617905560028151036106b9575f5b600281106144655750505f5b6002811061442c5750505f525f60205260405f2080545f825580614412575b505f60018201555f60028201555f6003820155614301600682016142fc8160048501612840565b614261565b61430d600a8201614261565b5f600e8201555f600f8201555f60108201556011810161432d8154612808565b90816143cf575b5050601281018054905f815581614396575b50506013018054905f81558161435a575050565b81600302916003830403611b40575f5260205f20908101905b81811061437e575050565b805f600392555f60018201555f600282015501614373565b81600302916003830403611b40575f5260205f20908101905b8181101561434657805f600392555f60018201555f6002820155016143af565b81601f5f93116001146143e65750555b5f80614334565b8183526020832061440291601f0160051c810190600101612840565b80825281602081209155556143df565b61442690825f5260205f2090810190612840565b5f6142d5565b806001600160a01b0361444160019385612d52565b90549060031b1c165f528160205261445e848360405f2001614764565b50016142b6565b8061447c614475600193856126f9565b51866145b3565b016142aa565b602060405180927fcc2a842d00000000000000000000000000000000000000000000000000000000825260406004830152816001600160a01b03816144df6144cd604483018a612219565b6003198382030160248401528a612219565b0392165afa5f918161450a575b506144ff57506020809101519101511090565b90505f8092500b1390565b9091506020813d60201161453f575b81614526602093836120e4565b810103126102375751805f0b810361023757905f6144ec565b3d9150614519565b8051906141006001600160a01b036020830151169167ffffffffffffffff606081604084015116920151169260405193849261458f602085019760a0895260c08601906121ae565b926040850152606084015260808301524660a083015203601f1981018352826120e4565b906040810191825115614661575f525f602052601460405f20019160208201916001600160a01b0380845116165f528360205260405f205493841561465a576001600160a01b0392518086115f1461464f57614610908096612691565b908380865116165f5260205260405f205551165f5260016020526001600160a01b038060405f20925116165f526020526141d460405f2091825461257f565b506146108580612691565b5050505050565b505050565b905f602091828151910182855af115610ef6575f513d6146ca57506001600160a01b0381163b155b6146955750565b6001600160a01b03907f5274afe7000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b6001141561468e565b6146f0906146f99260ff8151166040602083015192015192614821565b909291926148a3565b90565b6001810190825f528160205260405f2054155f1461404b5780546801000000000000000081101561067d5761475161473b826001879401855584612d52565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b906001820191815f528260205260405f20548015155f14614043575f198101818111611b405782545f19810191908211611b40578181036147ec575b505050805480156147d8575f1901906147b98282612d52565b8154905f199060031b1b19169055555f526020525f6040812055600190565b634e487b7160e01b5f52603160045260245ffd5b61480c6147fc61473b9386612d52565b90549060031b1c92839286612d52565b90555f528360205260405f20555f80806147a0565b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411614898579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610ef6575f516001600160a01b0381161561488e57905f905f90565b505f906001905f90565b5050505f9160039190565b6148ac816121ea565b806148b5575050565b6148be816121ea565b600181036148ee577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b6148f7816121ea565b6002810361492b57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b600390614937816121ea565b1461493f5750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffdfea26469706673582212201a22c42288f1ba0e9314d3b1e6e8b7aa4d4a62d815a181d9b949bb05ac25096564736f6c634300081b0033",
+ ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState[]\",\"name\":\"proofs\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes\",\"name\":\"challengerSig\",\"type\":\"bytes\"}],\"name\":\"challenge\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState[]\",\"name\":\"proofs\",\"type\":\"tuple[]\"}],\"name\":\"checkpoint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"name\":\"close\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structChannel\",\"name\":\"ch\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"initial\",\"type\":\"tuple\"}],\"name\":\"create\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"deposit\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structChannel\",\"name\":\"ch\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"initial\",\"type\":\"tuple\"}],\"name\":\"depositAndCreate\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip712Domain\",\"outputs\":[{\"internalType\":\"bytes1\",\"name\":\"fields\",\"type\":\"bytes1\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"version\",\"type\":\"string\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"verifyingContract\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"salt\",\"type\":\"bytes32\"},{\"internalType\":\"uint256[]\",\"name\":\"extensions\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"}],\"name\":\"getAccountsBalances\",\"outputs\":[{\"internalType\":\"uint256[][]\",\"name\":\"\",\"type\":\"uint256[][]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"}],\"name\":\"getChannelBalances\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"balances\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"name\":\"getChannelData\",\"outputs\":[{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"internalType\":\"structChannel\",\"name\":\"channel\",\"type\":\"tuple\"},{\"internalType\":\"enumChannelStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"address[]\",\"name\":\"wallets\",\"type\":\"address[]\"},{\"internalType\":\"uint256\",\"name\":\"challengeExpiry\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"lastValidState\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"accounts\",\"type\":\"address[]\"}],\"name\":\"getOpenChannels\",\"outputs\":[{\"internalType\":\"bytes32[][]\",\"name\":\"\",\"type\":\"bytes32[][]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"join\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState\",\"name\":\"candidate\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"internalType\":\"structState[]\",\"name\":\"proofs\",\"type\":\"tuple[]\"}],\"name\":\"resize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"withdraw\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"state\",\"type\":\"tuple\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"Challenged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"state\",\"type\":\"tuple\"}],\"name\":\"Checkpointed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"finalState\",\"type\":\"tuple\"}],\"name\":\"Closed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"wallet\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address[]\",\"name\":\"participants\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"adjudicator\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"challenge\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"nonce\",\"type\":\"uint64\"}],\"indexed\":false,\"internalType\":\"structChannel\",\"name\":\"channel\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enumStateIntent\",\"name\":\"intent\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"destination\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structAllocation[]\",\"name\":\"allocations\",\"type\":\"tuple[]\"},{\"internalType\":\"bytes[]\",\"name\":\"sigs\",\"type\":\"bytes[]\"}],\"indexed\":false,\"internalType\":\"structState\",\"name\":\"initial\",\"type\":\"tuple\"}],\"name\":\"Created\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"wallet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"EIP712DomainChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"Joined\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"name\":\"Opened\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"int256[]\",\"name\":\"deltaAllocations\",\"type\":\"int256[]\"}],\"name\":\"Resized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"wallet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawn\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"ChallengeNotExpired\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ChannelNotFinal\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"channelId\",\"type\":\"bytes32\"}],\"name\":\"ChannelNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DepositAlreadyFulfilled\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"expectedFulfilled\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actualFulfilled\",\"type\":\"uint256\"}],\"name\":\"DepositsNotFulfilled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ECDSAInvalidSignature\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"length\",\"type\":\"uint256\"}],\"name\":\"ECDSAInvalidSignatureLength\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"ECDSAInvalidSignatureS\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"available\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"required\",\"type\":\"uint256\"}],\"name\":\"InsufficientBalance\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAdjudicator\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAllocations\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidAmount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidChallengePeriod\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidChallengerSignature\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidParticipant\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidShortString\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidState\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidStateSignatures\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidStatus\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidValue\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"SafeERC20FailedOperation\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"str\",\"type\":\"string\"}],\"name\":\"StringTooLong\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TransferFailed\",\"type\":\"error\"}]",
+ Bin: "0x0x610160604052346101365760405161001860408261013a565b601181526020810190704e6974726f6c6974653a437573746f647960781b82526040519161004760408461013a565b600683526020830191650c0b8c8b8c8d60d21b835261006581610171565b6101205261007284610307565b61014052519020918260e05251902080610100524660a0526040519060208201927f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8452604083015260608201524660808201523060a082015260a081526100db60c08261013a565b5190206080523060c0526040516153549081610440823960805181614a97015260a05181614b4e015260c05181614a68015260e05181614ae601526101005181614b0c01526101205181611068015261014051816110940152f35b5f80fd5b601f909101601f19168101906001600160401b0382119082101761015d57604052565b634e487b7160e01b5f52604160045260245ffd5b908151602081105f146101eb575090601f8151116101ab57602081519101516020821061019c571790565b5f198260200360031b1b161790565b604460209160405192839163305a27a960e01b83528160048401528051918291826024860152018484015e5f828201840152601f01601f19168101030190fd5b6001600160401b03811161015d575f54600181811c911680156102fd575b60208210146102e957601f81116102b7575b50602092601f821160011461025857928192935f9261024d575b50508160011b915f199060031b1c1916175f5560ff90565b015190505f80610235565b601f198216935f8052805f20915f5b86811061029f5750836001959610610287575b505050811b015f5560ff90565b01515f1960f88460031b161c191690555f808061027a565b91926020600181928685015181550194019201610267565b5f8052601f60205f20910160051c810190601f830160051c015b8181106102de575061021b565b5f81556001016102d1565b634e487b7160e01b5f52602260045260245ffd5b90607f1690610209565b908151602081105f14610332575090601f8151116101ab57602081519101516020821061019c571790565b6001600160401b03811161015d57600154600181811c91168015610435575b60208210146102e957601f8111610402575b50602092601f82116001146103a157928192935f92610396575b50508160011b915f199060031b1c19161760015560ff90565b015190505f8061037d565b601f1982169360015f52805f20915f5b8681106103ea57508360019596106103d2575b505050811b0160015560ff90565b01515f1960f88460031b161c191690555f80806103c4565b919260206001819286850151815501940192016103b1565b60015f52601f60205f20910160051c810190601f830160051c015b81811061042a5750610363565b5f815560010161041d565b90607f169061035156fe60806040526004361015610011575f80fd5b5f3560e01c8062e2bb2c146123555780631474e410146122d1578063183b499814611af05780632f33c4d6146119365780634a7e7798146118ca5780635a9eb80e146118285780637f9ebbd7146111705780638340f5491461113957806384b0196e14611050578063bab3290a14610a6e578063d710e92f146108e0578063e617208c1461078d578063ecf668fd146102455763f3fef3a3146100b2575f80fd5b34610241576040600319360112610241576100cb6123cb565b60243590335f52600360205260405f20906001600160a01b0381165f528160205260405f205491838310610211576001600160a01b0392508282165f5260205260405f2061011a8482546137c5565b90551690816101bf575f80808084335af13d156101ba573d61013b8161275c565b90610149604051928361251b565b81525f60203d92013e5b15610187575b6040519081527fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb60203392a3005b907fbf182be8000000000000000000000000000000000000000000000000000000005f526004523360245260445260645ffd5b610153565b61020c6040517fa9059cbb0000000000000000000000000000000000000000000000000000000060208201523360248201528260448201526044815261020660648261251b565b83614fc1565b610159565b50507fcf479181000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b5f80fd5b346102415761025336612454565b835f52600260205260405f2091600383019160ff835416906005821015610779578115610766576004821461073e578535926004841015610241576102978461264f565b8361064257600f86019260ff84541690600181145f146102d9577ff525e320000000000000000000000000000000000000000000000000000000005f5260045ffd5b60020361068b57506001600160a01b036001870154169161030d6102fd368a612854565b61030686612af3565b9085614858565b15610642576103379260209260405180958194829363030232af60e21b84528d8d60048601612e43565b03915afa908115610680575f91610651575b501561064257610364925b600260ff19825416179055612ee8565b602082013560108201556011810161037f6040840184612f01565b9067ffffffffffffffff8211610559576103a38261039d85546129b8565b85612f4a565b5f90601f83116001146105de576103d192915f91836105d3575b50508160011b915f199060031b1c19161790565b90555b601281016103e56060840184612bb1565b91906103f18383612f8f565b905f5260205f205f915b83831061056d578686601387016104156080830183613011565b90916104218282613095565b5f9081526020812092805b83831061047257867f8cade4fe25d72146dc0dbe08ea2712bdcca7e2c996e2dce1e69f20e30ee1c5c361046d88604051918291602083526020830190612c6e565b0390a2005b61047c8183612f01565b9067ffffffffffffffff8211610559576104a08261049a89546129b8565b89612f4a565b5f90601f83116001146104ef57926104d5836001959460209487965f926104e45750508160011b915f199060031b1c19161790565b88555b0195019201919361042c565b013590508d806103bd565b601f19831691885f5260205f20925f5b8181106105415750936020936001969387969383889510610528575b505050811b0188556104d8565b01355f19600384901b60f8161c191690558c808061051b565b919360206001819287870135815501950192016104ff565b634e487b7160e01b5f52604160045260245ffd5b60036060826001600160a01b03610585600195612ffd565b166001600160a01b03198654161785556105a160208201612ffd565b6001600160a01b0385870191166001600160a01b031982541617905560408101356002860155019201920191906103fb565b0135905087806103bd565b601f19831691845f5260205f20925f5b81811061062a5750908460019594939210610611575b505050811b0190556103d4565b01355f19600384901b60f8161c19169055868080610604565b919360206001819287870135815501950192016105ee565b63baf3f0f760e01b5f5260045ffd5b610673915060203d602011610679575b61066b818361251b565b810190612de4565b87610349565b503d610661565b6040513d5f823e3d90fd5b909160206106bf916001600160a01b0360018a01541694604051938492839263030232af60e21b84528d8d60048601612e43565b0381865afa908115610680575f9161071f575b5015610642576106e18161264f565b156106f7575b50610364925f600e860155610354565b610714906107053688612854565b61070e84612af3565b91614858565b1561064257866106e7565b610738915060203d6020116106795761066b818361251b565b896106d2565b7ff525e320000000000000000000000000000000000000000000000000000000005f5260045ffd5b866379c1d89f60e11b5f5260045260245ffd5b634e487b7160e01b5f52602160045260245ffd5b34610241576020600319360112610241575f60606040516107ad816124c7565b81815282602082015282604082015201526107c66137d2565b506004355f52600260205260405f206107de81613770565b60ff60038301541691604051906107f660608361251b565b600282526040366020840137600481015f5b600281106108b2575050610823600f600e8301549201612af3565b6040519460a0865267ffffffffffffffff606061084d8751608060a08b01526101208a0190612613565b966001600160a01b0360208201511660c08a01528260408201511660e08a015201511661010087015260058110156107795785946108ae9461089b9260208801528682036040880152612613565b9160608501528382036080850152612659565b0390f35b806001600160a01b036108c76001938561394b565b90549060031b1c166108d9828761382d565b5201610808565b346102415760206003193601126102415760043567ffffffffffffffff811161024157610911903690600401612556565b8051906109366109208361253e565b9261092e604051948561251b565b80845261253e565b90610949601f19602085019301836138c4565b5f5b81518110156109dd576001600160a01b03610966828461382d565b51165f526003602052600160405f20016040519081602082549182815201915f5260205f20905f905b8082106109c557505050906109a98160019493038261251b565b6109b3828761382d565b526109be818661382d565b500161094b565b9091926001602081928654815201940192019061098f565b50509060405191829160208301906020845251809152604083019060408160051b85010192915f905b828210610a1557505050500390f35b9193909294603f19908203018252845190602080835192838152019201905f905b808210610a56575050506020806001929601920192018594939192610a06565b90919260208060019286518152019401920190610a36565b346102415760606003193601126102415760043560443567ffffffffffffffff811161024157610aa2903690600401612426565b825f93929352600260205260405f2090600382019060ff825416600581101561077957801561103d575f190161073e5760016024350361101557600d830154610fed57600f830194610b2b610af687612af3565b610afe614a5e565b906001600160a01b03610b10886143d0565b90549060031b1c169188610b25368888612778565b92614b74565b15610fc557610b3986612af3565b90610b8960405191610b4c60608461251b565b60028352610b5e6040602085016138c4565b6080840194610b6d86516137fc565b51610b77856137fc565b52610b81846137fc565b503691612778565b610b928261381d565b52610b9c8161381d565b50825260018401546001600160a01b0316604051906020610bbd818461251b565b5f8352601f19015f5b818110610fae575050610bf391602091604051808095819463030232af60e21b8352888c60048501613841565b03915afa908115610680575f91610f8f575b501561064257610c71610c1a600886016139be565b96610c4c88600c88019060206001916001600160a01b0380825116166001600160a01b03198554161784550151910155565b6005860180546001600160a01b03191633179055825190610c6c8261264f565b612ee8565b6020810151601085015560118401604082015180519067ffffffffffffffff821161055957610ca48261039d85546129b8565b602090601f8311600114610f2c57610cd292915f9183610f215750508160011b915f199060031b1c19161790565b90555b606060128501910151906020825192610cee8484612f8f565b01905f5260205f205f915b838310610ebe57505050506013830190516020815191610d198385613095565b01915f5260205f20915f905b828210610ddd57845460ff19166002178555602087610d858a6001600160a01b03610d4f8b6143d0565b90549060031b1c165f5260038452610d6d83600160405f2001614f59565b50836001600160a01b03825116910151908333614d06565b807fe8e915db7b3549b9e9e9b3e2ec2dc3edd1f76961504366998824836401f6846a8360405160018152a260405190807fd087f17acc177540af5f382bc30c65363705b90855144d285a822536ee11fdd15f80a28152f35b805180519067ffffffffffffffff821161055957610e0582610dff88546129b8565b88612f4a565b602090601f8311600114610e555792610e3b836001959460209487965f92610e4a5750508160011b915f199060031b1c19161790565b87555b01940191019092610d25565b015190508e806103bd565b90601f19831691875f52815f20925f5b818110610ea65750936020936001969387969383889510610e8e575b505050811b018755610e3e565b01515f1960f88460031b161c191690558d8080610e81565b92936020600181928786015181550195019301610e65565b60036020826040600194516001600160a01b0380825116166001600160a01b03198854161787556001600160a01b0384820151166001600160a01b0387890191166001600160a01b03198254161790550151600286015501920192019190610cf9565b015190508a806103bd565b90601f19831691845f52815f20925f5b818110610f775750908460019594939210610f5f575b505050811b019055610cd5565b01515f1960f88460031b161c19169055898080610f52565b92936020600181928786015181550195019301610f3c565b610fa8915060203d6020116106795761066b818361251b565b87610c05565b602090610fb96137d2565b82828701015201610bc6565b7f773a750f000000000000000000000000000000000000000000000000000000005f5260045ffd5b7f1b136079000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fa145c43e000000000000000000000000000000000000000000000000000000005f5260045ffd5b846379c1d89f60e11b5f5260045260245ffd5b34610241575f6003193601126102415761110b61108c7f000000000000000000000000000000000000000000000000000000000000000061502e565b6108ae6110b87f000000000000000000000000000000000000000000000000000000000000000061509f565b611119604051916110ca60208461251b565b5f83525f3681376040519586957f0f00000000000000000000000000000000000000000000000000000000000000875260e0602088015260e08701906125ee565b9085820360408701526125ee565b904660608501523060808501525f60a085015283820360c08501526125bb565b60606003193601126102415761114d6123cb565b602435906001600160a01b03821682036102415761116e9160443591614271565b005b346102415761117e36612454565b5050815f52600260205260405f2060ff600382015416600581101561077957801561181557600281036114e5575081356004811015610241576111c08161264f565b60038103610642576020830135801561064257608084019160026111e48487613011565b905003610fc5576112076111f785613770565b6112013688612854565b90614953565b15610fc55761121990600f8501612ee8565b60108301556011820161122f6040850185612f01565b9067ffffffffffffffff82116105595761124d8261039d85546129b8565b5f90601f83116001146114815761127a92915f91836114765750508160011b915f199060031b1c19161790565b90555b6012820161128e6060850185612bb1565b919061129a8383612f8f565b905f5260205f205f915b83831061141057505050506112bd601383019184613011565b90916112c98282613095565b5f9081526020812092805b83831061132f5750505050507fd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a704969161131b611315601261046d945b01612a72565b856146b8565b604051918291602083526020830190612c6e565b6113398183612f01565b9067ffffffffffffffff8211610559576113578261049a89546129b8565b5f90601f83116001146113a6579261138c836001959460209487965f9261139b5750508160011b915f199060031b1c19161790565b88555b019501920191936112d4565b013590508e806103bd565b601f19831691885f5260205f20925f5b8181106113f857509360209360019693879693838895106113df575b505050811b01885561138f565b01355f19600384901b60f8161c191690558d80806113d2565b919360206001819287870135815501950192016113b6565b60036060826001600160a01b03611428600195612ffd565b166001600160a01b031986541617855561144460208201612ffd565b6001600160a01b0385870191166001600160a01b031982541617905560408101356002860155019201920191906112a4565b0135905088806103bd565b601f19831691845f5260205f20925f5b8181106114cd57509084600195949392106114b4575b505050811b01905561127d565b01355f19600384901b60f8161c191690558780806114a7565b91936020600181928787013581550195019201611491565b60030361073e57600e810180544210156117e157823560048110156102415761150d8161264f565b600381036106425761152b61152184613770565b6112013687612854565b15610fc5575f61153f9255600f8301612ee8565b602082013560108201556011810161155a6040840184612f01565b9067ffffffffffffffff8211610559576115788261039d85546129b8565b5f90601f831160011461177d576115a592915f91836105d35750508160011b915f199060031b1c19161790565b90555b601281016115b96060840184612bb1565b91906115c58383612f8f565b905f5260205f205f915b8383106117175750505050601381016115eb6080840184613011565b90916115f78282613095565b5f9081526020812092805b8383106116415750505050507fd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a704969161131b611315601261046d9461130f565b61164b8183612f01565b9067ffffffffffffffff8211610559576116698261049a89546129b8565b5f90601f83116001146116ad579261169e836001959460209487965f9261139b5750508160011b915f199060031b1c19161790565b88555b01950192019193611602565b601f19831691885f5260205f20925f5b8181106116ff57509360209360019693879693838895106116e6575b505050811b0188556116a1565b01355f19600384901b60f8161c191690558d80806116d9565b919360206001819287870135815501950192016116bd565b60036060826001600160a01b0361172f600195612ffd565b166001600160a01b031986541617855561174b60208201612ffd565b6001600160a01b0385870191166001600160a01b031982541617905560408101356002860155019201920191906115cf565b601f19831691845f5260205f20925f5b8181106117c957509084600195949392106117b0575b505050811b0190556115a8565b01355f19600384901b60f8161c191690558680806117a3565b9193602060018192878701358155019501920161178d565b507fd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a704969161131b611315601261046d9461130f565b836379c1d89f60e11b5f5260045260245ffd5b346102415760406003193601126102415760043560243567ffffffffffffffff81116102415761185c903690600401612556565b61186681516138e0565b5f5b82518110156118b457600190845f526002602052601460405f20016001600160a01b0380611896848861382d565b5116165f5260205260405f20546118ad828561382d565b5201611868565b604051602080825281906108ae908201856125bb565b346102415760406003193601126102415760043567ffffffffffffffff811161024157608060031982360301126102415760243567ffffffffffffffff81116102415760a060031982360301126102415760209161192e91600401906004016139e5565b604051908152f35b346102415760406003193601126102415760043567ffffffffffffffff8111610241576119679036906004016123f5565b60243567ffffffffffffffff8111610241576119879036906004016123f5565b9190926119938261253e565b936119a1604051958661251b565b8285526119ad8361253e565b936119c0601f19602088019601866138c4565b5f5b848110611a5957858760405191829160208301906020845251809152604083019060408160051b85010192915f905b828210611a0057505050500390f35b9193909294603f19908203018252845190602080835192838152019201905f905b808210611a415750505060208060019296019201920185949391926119f1565b90919260208060019286518152019401920190611a21565b611a62826138e0565b611a6c828961382d565b52611a77818861382d565b505f5b828110611a8a57506001016119c2565b6001906001600160a01b03611aa8611aa3858a8a613911565b612ffd565b165f52600360205260405f206001600160a01b03611aca611aa384888a613911565b165f5260205260405f2054611ae982611ae3868d61382d565b5161382d565b5201611a7a565b3461024157611afe36612454565b9190835f52600260205260405f209360ff60038601541660058110156107795780156122be577ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0161073e57831561064257823592600484101561024157611b658461264f565b6002840361064257823593609e1984360301948581121561024157611b8d9085013690612854565b966020830135602089015160018101809111611d81578103610642576060890193611bb8855161491d565b6060810191611bd9611bd4611bcd8585612bb1565b36916127cc565b61491d565b611bef611be585613770565b6112013685612854565b15610fc557611c016040830183612f01565b810195906020818803126102415780359067ffffffffffffffff821161024157019686601f8901121561024157873596611c3a8861253e565b98611c486040519a8b61251b565b888a5260208a01906020829a60051b82010192831161024157602001905b8282106122ae5750505051611c7e611bcd8686612bb1565b90600289510361064257611cb0816040611ca781611c9e611ce7966137fc565b5101519261381d565b51015190612edb565b91611ce1611cd1611cc08c6137fc565b51611cca8d61381d565b51906149d1565b916040611ca781611c9e846137fc565b926149d1565b03612286575f198b019b8b8d11611d8157611d018d61253e565b9c806040519e8f90611d13908261251b565b52611d1d9061253e565b601f19018d5f5b82811061226d5750505060015b8c811015611d95578060051b8b01358c811215610241575f19820191908c01818311611d81578f92600193611d6a611d7a933690612854565b611d74838361382d565b5261382d565b5001611d31565b634e487b7160e01b5f52601160045260245ffd5b5060208d611dc9926001600160a01b0360018a0154169060405180958194829363030232af60e21b84528d60048501613841565b03915afa908115610680575f9161224e575b501561064257611dee90600f8601612ee8565b601084015560118301611e046040830183612f01565b9067ffffffffffffffff821161055957611e228261039d85546129b8565b5f90601f83116001146121ea57611e4f92915f91836121df5750508160011b915f199060031b1c19161790565b90555b60128301611e608383612bb1565b9190611e6c8383612f8f565b905f5260205f205f915b838310612179575050505060138301611e926080830183613011565b9091611e9e8282613095565b5f9081526020812092805b838310612098575050505050611ec291611bcd91612bb1565b9060068101916001600160a01b038354165f5b6002811061203f57505f5b60028110611f9f575050600a5f9201915b60028110611f5c575050505060405191602083019060208452518091526040830191905f5b818110611f4657857ff3b6c524f73df7344d9fcf2f960a57aba7fba7e292d8b79ed03d786f7b2b112f86860387a2005b8251845260209384019390920191600101611f16565b806040611f6b6001938561382d565b51015182611f79838861396a565b5001556040611f88828561382d565b51015182611f96838761396a565b50015501611ef1565b5f611fad828997969761382d565b5112611fbf575b600101939293611ee0565b6001600160a01b03611fd4826004880161394b565b90549060031b1c1690611fe7818961382d565b517f80000000000000000000000000000000000000000000000000000000000000008114611d81576001926120389160405191612023836124ab565b82528560208301525f0360408201528a614ea5565b9050611fb4565b805f6120506001938a98979861382d565b5113612060575b01939293611ed5565b6120936001600160a01b036120788360048a0161394b565b90549060031b1c16848b61208c858d61382d565b5192614d06565b612057565b6120a28183612f01565b9067ffffffffffffffff8211610559576120c08261049a89546129b8565b5f90601f831160011461210f57926120f5836001959460209487965f926121045750508160011b915f199060031b1c19161790565b88555b01950192019193611ea9565b013590505f806103bd565b601f19831691885f5260205f20925f5b8181106121615750936020936001969387969383889510612148575b505050811b0188556120f8565b01355f19600384901b60f8161c191690555f808061213b565b9193602060018192878701358155019501920161211f565b60036060826001600160a01b03612191600195612ffd565b166001600160a01b03198654161785556121ad60208201612ffd565b6001600160a01b0385870191166001600160a01b03198254161790556040810135600286015501920192019190611e76565b013590508a806103bd565b601f19831691845f5260205f20925f5b818110612236575090846001959493921061221d575b505050811b019055611e52565b01355f19600384901b60f8161c19169055898080612210565b919360206001819287870135815501950192016121fa565b612267915060203d6020116106795761066b818361251b565b89611ddb565b602091828261227a6137d2565b92010152018e90611d24565b7f52e4cb1c000000000000000000000000000000000000000000000000000000005f5260045ffd5b8135815260209182019101611c66565b506379c1d89f60e11b5f5260045260245ffd5b346102415760806003193601126102415760243567ffffffffffffffff81116102415760a060031982360301126102415760443567ffffffffffffffff8111610241576123229036906004016123f5565b916064359267ffffffffffffffff84116102415761234761116e943690600401612426565b9390926004016004356130e1565b6080600319360112610241576123696123cb565b6044359067ffffffffffffffff82116102415760806003198336030112610241576064359167ffffffffffffffff83116102415760a06003198436030112610241576020926123bf61192e936024359033614271565b600401906004016139e5565b600435906001600160a01b038216820361024157565b35906001600160a01b038216820361024157565b9181601f840112156102415782359167ffffffffffffffff8311610241576020808501948460051b01011161024157565b9181601f840112156102415782359167ffffffffffffffff8311610241576020838186019501011161024157565b6060600319820112610241576004359160243567ffffffffffffffff81116102415760a0600319828503011261024157600401916044359067ffffffffffffffff8211610241576124a7916004016123f5565b9091565b6060810190811067ffffffffffffffff82111761055957604052565b6080810190811067ffffffffffffffff82111761055957604052565b60a0810190811067ffffffffffffffff82111761055957604052565b6040810190811067ffffffffffffffff82111761055957604052565b90601f601f19910116810190811067ffffffffffffffff82111761055957604052565b67ffffffffffffffff81116105595760051b60200190565b9080601f8301121561024157813561256d8161253e565b9261257b604051948561251b565b81845260208085019260051b82010192831161024157602001905b8282106125a35750505090565b602080916125b0846123e1565b815201910190612596565b90602080835192838152019201905f5b8181106125d85750505090565b82518452602093840193909201916001016125cb565b90601f19601f602080948051918291828752018686015e5f8582860101520116010190565b90602080835192838152019201905f5b8181106126305750505090565b82516001600160a01b0316845260209384019390920191600101612623565b6004111561077957565b80516126648161264f565b825260208101516020830152612689604082015160a0604085015260a08401906125ee565b906060810151918381036060850152602080845192838152019301905f5b8181106127145750505060800151916080818303910152815180825260208201916020808360051b8301019401925f915b8383106126e757505050505090565b909192939460208061270583601f19866001960301875289516125ee565b970193019301919392906126d8565b909193602061275260019287519060406060926001600160a01b0381511683526001600160a01b036020820151166020840152015160408201520190565b95019291016126a7565b67ffffffffffffffff811161055957601f01601f191660200190565b9291926127848261275c565b91612792604051938461251b565b829481845281830111610241578281602093845f960137010152565b9080601f83011215610241578160206127c993359101612778565b90565b9291926127d88261253e565b936127e6604051958661251b565b606060208685815201930282019181831161024157925b82841061280a5750505050565b606084830312610241576020606091604051612825816124ab565b61282e876123e1565b815261283b8388016123e1565b83820152604087013560408201528152019301926127fd565b919060a0838203126102415760405161286c816124e3565b80938035600481101561024157825260208101356020830152604081013567ffffffffffffffff811161024157836128a59183016127ae565b6040830152606081013567ffffffffffffffff811161024157810183601f8201121561024157838160206128db933591016127cc565b606083015260808101359067ffffffffffffffff821161024157019180601f8401121561024157823561290d8161253e565b9361291b604051958661251b565b81855260208086019260051b820101918383116102415760208201905b83821061294a57505050505060800152565b813567ffffffffffffffff81116102415760209161296d878480948801016127ae565b815201910190612938565b90602082549182815201915f5260205f20905f5b8181106129995750505090565b82546001600160a01b031684526020909301926001928301920161298c565b90600182811c921680156129e6575b60208310146129d257565b634e487b7160e01b5f52602260045260245ffd5b91607f16916129c7565b5f92918154916129ff836129b8565b8083529260018116908115612a545750600114612a1b57505050565b5f9081526020812093945091925b838310612a3a575060209250010190565b600181602092949394548385870101520191019190612a29565b9050602094955060ff1991509291921683830152151560051b010190565b908154612a7e8161253e565b92612a8c604051948561251b565b81845260208401905f5260205f205f915b838310612aaa5750505050565b60036020600192604051612abd816124ab565b6001600160a01b0386541681526001600160a01b0385870154168382015260028601546040820152815201920192019190612a9d565b90604051612b00816124e3565b6004819360ff815416612b128161264f565b835260018101546020840152604051612b3981612b3281600286016129f0565b038261251b565b6040840152612b4a60038201612a72565b606084015201908154612b5c8161253e565b92612b6a604051948561251b565b81845260208401905f5260205f205f915b838310612b8c575050505060800152565b600160208192604051612ba381612b3281896129f0565b815201920192019190612b7b565b903590601e1981360301821215610241570180359067ffffffffffffffff82116102415760200191606082023603831361024157565b9035601e198236030181121561024157016020813591019167ffffffffffffffff821161024157813603831361024157565b601f8260209493601f1993818652868601375f8582860101520116010190565b9035601e198236030181121561024157016020813591019167ffffffffffffffff8211610241578160051b3603831361024157565b8035600481101561024157612c828161264f565b825260208101356020830152612caf612c9e6040830183612be7565b60a0604086015260a0850191612c19565b906060810135601e19823603018112156102415781016020813591019267ffffffffffffffff82116102415760608202360384136102415784810360608601528181526020019392905f5b818110612d7a57505050806080612d12920190612c39565b90916080818503910152808352602083019260208260051b82010193835f925b848410612d425750505050505090565b909192939495602080612d6a83601f198660019603018852612d648b88612be7565b90612c19565b9801940194019294939190612d32565b909193946060806001926001600160a01b03612d95896123e1565b1681526001600160a01b03612dac60208a016123e1565b1660208201526040888101359082015201969501929101612cfa565b929190612ddf602091604086526040860190612c6e565b930152565b90816020910312610241575180151581036102415790565b9060808152606067ffffffffffffffff6002612e1b6080850186612978565b948260018201546001600160a01b038116602088015260a01c16604086015201541691015290565b91612e59612e6792606085526060850190612dfc565b908382036020850152612c6e565b906040818303910152828152602081019260208160051b83010193835f91609e1982360301945b848410612e9f575050505050505090565b90919293949596601f19828203018352873587811215610241576020612eca60019387839401612c6e565b990193019401929195949390612e8e565b91908201809211611d8157565b90612ef28161264f565b60ff60ff198354169116179055565b903590601e1981360301821215610241570180359067ffffffffffffffff82116102415760200191813603831361024157565b818110612f3f575050565b5f8155600101612f34565b9190601f8111612f5957505050565b612f83925f5260205f20906020601f840160051c83019310612f85575b601f0160051c0190612f34565b565b9091508190612f76565b9068010000000000000000811161055957815491818155828210612fb257505050565b82600302926003840403611d815781600302916003830403611d81575f5260205f2091820191015b818110612fe5575050565b805f600392555f60018201555f600282015501612fda565b356001600160a01b03811681036102415790565b903590601e1981360301821215610241570180359067ffffffffffffffff821161024157602001918160051b3603831361024157565b61305181546129b8565b908161305b575050565b81601f5f931160011461306c575055565b8183526020832061308891601f0160051c810190600101612f34565b8082528160208120915555565b90680100000000000000008111610559578154918181558282106130b857505050565b5f5260205f2091820191015b8181106130cf575050565b806130db600192613047565b016130c4565b93909195949295845f52600260205260405f2091600383019760ff89541692600584101561077957831561375d575f600385148015613751575b61073e5786359360048510159889610241576131368661264f565b600386146106425761316361317161317893613152368d612854565b9361316a8c60405195868092612978565b038561251b565b3691612778565b918c6143fd565b600f86019460ff86541691610779576001146136aa5787906131998161264f565b600181036135575750610241576131af8361264f565b600183036134c35750506131d56131c63686612854565b6131cf84612af3565b90614651565b15610642575b6131f667ffffffffffffffff600185015460a01c1642612edb565b9485600e8501556102415761320a91612ee8565b60208201356010820155601181016132256040840184612f01565b9067ffffffffffffffff8211610559576132438261039d85546129b8565b5f90601f831160011461345f5761327092915f91836121045750508160011b915f199060031b1c19161790565b90555b601281016132846060840184612bb1565b91906132908383612f8f565b905f5260205f205f915b8383106133f95750505050601301946132b66080830183613011565b90966132c28282613095565b5f9081526020812097805b83831061331c57505050507f44c1980976c3af1eb75b2a3b7d8c7e01f69168c0fe45dd229faf143233722e1793949550600360ff1982541617905561331760405192839283612dc8565b0390a2565b6133268183612f01565b9067ffffffffffffffff82116105595761334b828d61334581546129b8565b90612f4a565b5f90601f831160011461338f5792613380836001959460209487965f926121045750508160011b915f199060031b1c19161790565b8d555b019a01920191986132cd565b601f198316918d5f5260205f20925f5b8181106133e157509360209360019693879693838895106133c8575b505050811b018d55613383565b01355f19600384901b60f8161c191690555f80806133bb565b9193602060018192878701358155019501920161339f565b60036060826001600160a01b03613411600195612ffd565b166001600160a01b031986541617855561342d60208201612ffd565b6001600160a01b0385870191166001600160a01b0319825416179055604081013560028601550192019201919061329a565b601f19831691845f5260205f20925f5b8181106134ab5750908460019594939210613492575b505050811b019055613273565b01355f19600384901b60f8161c191690555f8080613485565b9193602060018192878701358155019501920161346f565b6001600160a01b03600186015416916134e86134df3689612854565b61030687612af3565b15610642576135129260209260405180958194829363030232af60e21b84528c8c60048601612e43565b03915afa908115610680575f91613538575b506131db5763baf3f0f760e01b5f5260045ffd5b613551915060203d6020116106795761066b818361251b565b5f613524565b90506135628161264f565b80613623575086610241576135768361264f565b82610642576135916135883688612854565b6131cf86612af3565b1561359f575b50505b6131db565b6001600160a01b03600186015416916135bb6134df3689612854565b15610642576135e59260209260405180958194829363030232af60e21b84528c8c60048601612e43565b03915afa908115610680575f91613604575b5015610642575f80613597565b61361d915060203d6020116106795761066b818361251b565b5f6135f7565b60029197506136318161264f565b036106425761363f8261264f565b60018214610642575f956136528361264f565b82613673576001600160a01b03600186015416916134e86134df3689612854565b505093505f936136828161264f565b60028103610642576136976131c63686612854565b61359a5763baf3f0f760e01b5f5260045ffd5b505050509193949550506136cc91506131cf6136c63685612854565b91612af3565b156106425761331781613709611315611bcd60607fd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a70496960184612bb1565b837f44c1980976c3af1eb75b2a3b7d8c7e01f69168c0fe45dd229faf143233722e176040518061373a428683612dc8565b0390a2604051918291602083526020830190612c6e565b50505f6004851461311b565b876379c1d89f60e11b5f5260045260245ffd5b9060405161377d816124c7565b606067ffffffffffffffff6002839560405161379d81612b328185612978565b85528260018201546001600160a01b038116602088015260a01c166040860152015416910152565b91908203918211611d8157565b604051906137df826124e3565b60606080835f81525f602082015282604082015282808201520152565b8051156138095760200190565b634e487b7160e01b5f52603260045260245ffd5b8051600110156138095760400190565b80518210156138095760209160051b010190565b9161385761386592606085526060850190612dfc565b908382036020850152612659565b906040818303910152815180825260208201916020808360051b8301019401925f915b83831061389757505050505090565b90919293946020806138b583601f1986600196030187528951612659565b97019301930191939290613888565b5f5b8281106138d257505050565b6060828201526020016138c6565b906138ea8261253e565b6138f7604051918261251b565b828152601f19613907829461253e565b0190602036910137565b91908110156138095760051b0190565b3567ffffffffffffffff811681036102415790565b359067ffffffffffffffff8216820361024157565b60028210156138095701905f90565b9190811015613809576060020190565b9060028110156138095760011b01905f90565b91906139ab576020816001600160a01b03806001945116166001600160a01b03198554161784550151910155565b634e487b7160e01b5f525f60045260245ffd5b906040516139cb816124ff565b6020600182946001600160a01b0381541684520154910152565b9060026139f28380613011565b905014801590614249575b801561421b575b80156141d2575b61101557602082016001600160a01b03613a2482612ffd565b16156141aa576040830192610e1067ffffffffffffffff613a4486613921565b161061418257823593600485101561024157613a5f8561264f565b600185036106425760208401359081610642576080833603126102415760405191613a89836124c7565b833567ffffffffffffffff811161024157613aa79036908601612556565b8352613ab2856123e1565b6020840152613ac082613936565b6040840152613ae06060850193613ad685613936565b60608201526149ec565b96875f52600260205260ff600360405f2001541660058110156107795761073e57608087016001613b11828a613011565b905003610fc557613b223689612854565b613b2a614a5e565b90613b35838b613011565b156138095780613b4491612f01565b919092613b518a80613011565b9490941561380957610b258e91613b6a613b7297612ffd565b953691612778565b15610fc55760608801926002613b88858b612bb1565b90500361228657895f52600260205260405f2092613ba68880613011565b9067ffffffffffffffff821161055957680100000000000000008211610559578554828755808310614166575b50855f5260205f205f5b83811061414b5750505050613ccf90600185016001600160a01b03613c018c612ffd565b166001600160a01b0319825416178155613c1a88613921565b7fffffffff0000000000000000ffffffffffffffffffffffffffffffffffffffff7bffffffffffffffff000000000000000000000000000000000000000083549260a01b1691161790556002850167ffffffffffffffff613c7a8a613921565b82547fffffffffffffffffffffffffffffffffffffffffffffffff000000000000000016911617905560038501805460ff191660011790556004850180546001600160a01b03191633179055600f8501612ee8565b601083015560118201613ce560408a018a612f01565b9067ffffffffffffffff821161055957613d038261039d85546129b8565b5f90601f83116001146140e757613d3092915f91836121045750508160011b915f199060031b1c19161790565b90555b60128201613d41848a612bb1565b9190613d4d8383612f8f565b905f5260205f205f915b8383106140815750505050613d70601383019189613011565b9091613d7c8282613095565b5f9081526020812092805b838310613fab5750505050505f91600a600683019201925b60028110613f1e575050613db5613de0916139be565b80929060206001916001600160a01b0380825116166001600160a01b03198554161784550151910155565b613dea8480613011565b9190911561380957613e3d906001600160a01b03613e0c8a9798969594612ffd565b165f526003602052613e2486600160405f2001614f59565b5060206001600160a01b03825116910151908633614d06565b6040519260408452613e5360c085019680612c39565b608060408701529687905260e08501965f5b818110613eea575050509467ffffffffffffffff613ecf859482613ec4613ee4966001600160a01b03613eb97f4dd0384c1acc40a5edb69575b4a1caa43c2c2852ef96f7ecfc4a6705ddb8ccc79c9d6123e1565b1660608a0152613936565b166080870152613936565b1660a084015282810360208401523396612c6e565b0390a390565b91965091929394966020806001926001600160a01b03613f098b6123e1565b16815201970191019189969795949392613e65565b80613f838a6040613f5484613f4288613f4e6020613f4860019b613f42858b612bb1565b9061395a565b01612ffd565b95612bb1565b01356001600160a01b0360405192613f6b846124ff565b1682526020820152613f7d838761396a565b9061397d565b613fa5604051613f92816124ff565b5f81525f6020820152613f7d838861396a565b01613d9f565b613fb58183612f01565b9067ffffffffffffffff821161055957613fd38261049a89546129b8565b5f90601f83116001146140175792614008836001959460209487965f926121045750508160011b915f199060031b1c19161790565b88555b01950192019193613d87565b601f19831691885f5260205f20925f5b8181106140695750936020936001969387969383889510614050575b505050811b01885561400b565b01355f19600384901b60f8161c191690555f8080614043565b91936020600181928787013581550195019201614027565b60036060826001600160a01b03614099600195612ffd565b166001600160a01b03198654161785556140b560208201612ffd565b6001600160a01b0385870191166001600160a01b03198254161790556040810135600286015501920192019190613d57565b601f19831691845f5260205f20925f5b818110614133575090846001959493921061411a575b505050811b019055613d33565b01355f19600384901b60f8161c191690555f808061410d565b919360206001819287870135815501950192016140f7565b600190602061415985612ffd565b9401938184015501613bdd565b61417c90875f528360205f209182019101612f34565b5f613bd3565b7fb4e12433000000000000000000000000000000000000000000000000000000005f5260045ffd5b7fea9e70ce000000000000000000000000000000000000000000000000000000005f5260045ffd5b506141dd8280613011565b15613809576141eb90612ffd565b6141f58380613011565b60011015613809576001600160a01b036142126020829301612ffd565b16911614613a0b565b506142268280613011565b600110156138095761424260206001600160a01b039201612ffd565b1615613a04565b506142548280613011565b156138095761426a6001600160a01b0391612ffd565b16156139fd565b9082156143a8576001600160a01b031691821591821561437157813403614349577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7916001600160a01b036020925b1693845f526003835260405f20865f52835260405f206142e1838254612edb565b9055156142f2575b604051908152a3565b6143446040517f23b872dd00000000000000000000000000000000000000000000000000000000848201523360248201523060448201528260648201526064815261433e60848261251b565b86614fc1565b6142e9565b7faa7feadc000000000000000000000000000000000000000000000000000000005f5260045ffd5b34614349577f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7916001600160a01b036020926142c0565b7f2c5211c6000000000000000000000000000000000000000000000000000000005f5260045ffd5b805460011015613809575f52600160205f2001905f90565b8054821015613809575f5260205f2001905f90565b919092825f52600260205261441c61441760405f20613770565b6149ec565b9084516144288161264f565b6020860194855193604088019461446586519461445760608c0196875190604051958694602086019889614e26565b03601f19810183528261251b565b51902060405160208101918252604080820152600960608201527f6368616c6c656e676500000000000000000000000000000000000000000000006080820152608081526144b460a08261251b565b5190206144d66144d06144c7868461519b565b909291926151d5565b87614e66565b614647577f19457468657265756d205369676e6564204d6573736167653a0a3332000000005f52601c526145196145136144c785603c5f2061519b565b86614e66565b61463e576146129661460d9661460495604294614534614a5e565b9351926145408461264f565b519151602081519101209051604051614569816144576020820194602086526040830190614dc1565b5190209160405193602085019586527f74875af04779d70f933aef147d5751a32a32b3fa275f5022499f396ea394cf5360408601526145a78161264f565b6060850152608084015260a083015260c082015260c081526145ca60e08261251b565b519020604051917f19010000000000000000000000000000000000000000000000000000000000008352600283015260228201522061519b565b909391936151d5565b614e66565b612f83577f61a44f6e000000000000000000000000000000000000000000000000000000005f5260045ffd5b50505050505050565b5050505050505050565b60405161466e816144576020820194602086526040830190612659565b5190209060405161468f816144576020820194602086526040830190612659565b5190201490565b60048101905b8181106146a7575050565b5f808255600182015560020161469c565b90815f52600260205260405f209060038201600460ff198254161790556002815103610642575f5b6002811061483b5750505f5b600281106148015750505f52600260205260405f2080545f8255806147e7575b505f60018201555f60028201555f6003820155614738600682016147338160048501612f34565b614696565b614744600a8201614696565b5f600e8201555f600f8201555f601082015561476260118201613047565b601281018054905f8155816147ae575b50506013018054905f815581614786575050565b5f5260205f20908101905b81811061479c575050565b806147a8600192613047565b01614791565b81600302916003830403611d81575f5260205f20908101905b8181101561477257805f600392555f60018201555f6002820155016147c7565b6147fb90825f5260205f2090810190612f34565b5f61470c565b806001600160a01b03614816600193856143e8565b90549060031b1c165f526003602052614834848360405f20016150d6565b50016146ec565b8061485261484b6001938561382d565b5186614ea5565b016146e0565b602060405180927f7f7d6ab500000000000000000000000000000000000000000000000000000000825260406004830152816001600160a01b03816148b56148a3604483018a612659565b6003198382030160248401528a612659565b0392165afa5f91816148e0575b506148d557506020809101519101511090565b90505f8092500b1390565b9091506020813d602011614915575b816148fc6020938361251b565b810103126102415751805f0b810361024157905f6148c2565b3d91506148ef565b6002815103610642576001600160a01b036020614948828261493e866137fc565b510151169361381d565b510151160361064257565b60808201916002835151036149ca5761496b826149ec565b915f5b60028110614980575050505050600190565b6149b461498b614a5e565b61499683885161382d565b516001600160a01b036149aa85875161382d565b5116918787614b74565b156149c15760010161496e565b50505050505f90565b5050505f90565b9190915f8382019384129112908015821691151617611d8157565b805190614a586001600160a01b036020830151169167ffffffffffffffff6060816040840151169201511692604051938492614a34602085019760a0895260c0860190612613565b926040850152606084015260808301524660a083015203601f19810183528261251b565b51902090565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016301480614b4b575b15614ab9577f000000000000000000000000000000000000000000000000000000000000000090565b60405160208101907f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f82527f000000000000000000000000000000000000000000000000000000000000000060408201527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a082015260a08152614a5860c08261251b565b507f00000000000000000000000000000000000000000000000000000000000000004614614a90565b926001600160a01b03949193805193614b8c8561264f565b6020820195865194614bd58960408601988951614bbe606089019a6144578c5160405194859360208501978c89614e26565b51902092614bcc888561519b565b909491946151d5565b1699168914614cf8577f19457468657265756d205369676e6564204d6573736167653a0a3332000000005f52601c52876001600160a01b03614c1d6144c787603c5f2061519b565b1614614ceb577fdeeda0875e527d63774890b89d23bff91e0ec84761e45d2b578ee592780095bb8214614cdf576001600160a01b0396614cd0966144c7966042955192614c698461264f565b519151602081519101209051604051614c92816144576020820194602086526040830190614dc1565b519020916040519360208501957fb02e61f8dbbfba070321cdff64845b04358ee41db88ba372ef47e352446f4b9c875260408601526145a78161264f565b1614614cda575f90565b600190565b50505050505050505f90565b5050505050505050600190565b505050505050505050600190565b8315614dbb576001600160a01b03165f52600360205260405f206001600160a01b0383165f528060205260405f2054848110614d8b5784614d46916137c5565b906001600160a01b0384165f5260205260405f20555f5260026020526001600160a01b03601460405f200191165f52602052614d8760405f20918254612edb565b9055565b84907fcf479181000000000000000000000000000000000000000000000000000000005f5260045260245260445ffd5b50505050565b90602080835192838152019201905f5b818110614dde5750505090565b9091926020614e1c60019286519060406060926001600160a01b0381511683526001600160a01b036020820151166020840152015160408201520190565b9401929101614dd1565b939092614e58926127c996948652614e3d8161264f565b6020860152604085015260a0606085015260a08401906125ee565b916080818403910152614dc1565b905f5b82518110156149ca576001600160a01b03614e84828561382d565b51166001600160a01b03831614614e9d57600101614e69565b505050600190565b906040810191825115614f54575f526002602052601460405f20019160208201916001600160a01b0380845116165f528360205260405f2054938415614f4d576001600160a01b0392518086115f14614f4257614f039080966137c5565b908380865116165f5260205260405f205551165f5260036020526001600160a01b038060405f20925116165f52602052614d8760405f20918254612edb565b50614f0385806137c5565b5050505050565b505050565b6001810190825f528160205260405f2054155f146149ca5780546801000000000000000081101561055957614fae614f988260018794018555846143e8565b819391549060031b91821b915f19901b19161790565b905554915f5260205260405f2055600190565b905f602091828151910182855af115610680575f513d61502557506001600160a01b0381163b155b614ff05750565b6001600160a01b03907f5274afe7000000000000000000000000000000000000000000000000000000005f521660045260245ffd5b60011415614fe9565b60ff811461508d5760ff811690601f8211615065576040519161505260408461251b565b6020808452838101919036833783525290565b7fb3512b0c000000000000000000000000000000000000000000000000000000005f5260045ffd5b506040516127c981612b32815f6129f0565b60ff81146150c35760ff811690601f8211615065576040519161505260408461251b565b506040516127c981612b328160016129f0565b906001820191815f528260205260405f20548015155f14615193575f198101818111611d815782545f19810191908211611d815781810361515e575b5050508054801561514a575f19019061512b82826143e8565b8154905f199060031b1b19169055555f526020525f6040812055600190565b634e487b7160e01b5f52603160045260245ffd5b61517e61516e614f9893866143e8565b90549060031b1c928392866143e8565b90555f528360205260405f20555f8080615112565b505050505f90565b81519190604183036151cb576151c49250602082015190606060408401519301515f1a9061529c565b9192909190565b50505f9160029190565b6151de8161264f565b806151e7575050565b6151f08161264f565b60018103615220577ff645eedf000000000000000000000000000000000000000000000000000000005f5260045ffd5b6152298161264f565b6002810361525d57507ffce698f7000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b6003906152698161264f565b146152715750565b7fd78bce0c000000000000000000000000000000000000000000000000000000005f5260045260245ffd5b91907f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08411615313579160209360809260ff5f9560405194855216868401526040830152606082015282805260015afa15610680575f516001600160a01b0381161561530957905f905f90565b505f906001905f90565b5050505f916003919056fea2646970667358221220a42ae78844a10ed8f2b9c143b570a4d8ecbb65dda4c532c3bdd322fa72ea304064736f6c634300081b0033",
}
// CustodyABI is the input ABI used to generate the binding from.
@@ -233,6 +226,76 @@ func (_Custody *CustodyTransactorRaw) Transact(opts *bind.TransactOpts, method s
return _Custody.Contract.contract.Transact(opts, method, params...)
}
+// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e.
+//
+// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)
+func (_Custody *CustodyCaller) Eip712Domain(opts *bind.CallOpts) (struct {
+ Fields [1]byte
+ Name string
+ Version string
+ ChainId *big.Int
+ VerifyingContract common.Address
+ Salt [32]byte
+ Extensions []*big.Int
+}, error) {
+ var out []interface{}
+ err := _Custody.contract.Call(opts, &out, "eip712Domain")
+
+ outstruct := new(struct {
+ Fields [1]byte
+ Name string
+ Version string
+ ChainId *big.Int
+ VerifyingContract common.Address
+ Salt [32]byte
+ Extensions []*big.Int
+ })
+ if err != nil {
+ return *outstruct, err
+ }
+
+ outstruct.Fields = *abi.ConvertType(out[0], new([1]byte)).(*[1]byte)
+ outstruct.Name = *abi.ConvertType(out[1], new(string)).(*string)
+ outstruct.Version = *abi.ConvertType(out[2], new(string)).(*string)
+ outstruct.ChainId = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int)
+ outstruct.VerifyingContract = *abi.ConvertType(out[4], new(common.Address)).(*common.Address)
+ outstruct.Salt = *abi.ConvertType(out[5], new([32]byte)).(*[32]byte)
+ outstruct.Extensions = *abi.ConvertType(out[6], new([]*big.Int)).(*[]*big.Int)
+
+ return *outstruct, err
+
+}
+
+// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e.
+//
+// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)
+func (_Custody *CustodySession) Eip712Domain() (struct {
+ Fields [1]byte
+ Name string
+ Version string
+ ChainId *big.Int
+ VerifyingContract common.Address
+ Salt [32]byte
+ Extensions []*big.Int
+}, error) {
+ return _Custody.Contract.Eip712Domain(&_Custody.CallOpts)
+}
+
+// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e.
+//
+// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions)
+func (_Custody *CustodyCallerSession) Eip712Domain() (struct {
+ Fields [1]byte
+ Name string
+ Version string
+ ChainId *big.Int
+ VerifyingContract common.Address
+ Salt [32]byte
+ Extensions []*big.Int
+}, error) {
+ return _Custody.Contract.Eip712Domain(&_Custody.CallOpts)
+}
+
// GetAccountsBalances is a free data retrieval call binding the contract method 0x2f33c4d6.
//
// Solidity: function getAccountsBalances(address[] accounts, address[] tokens) view returns(uint256[][])
@@ -297,7 +360,7 @@ func (_Custody *CustodyCallerSession) GetChannelBalances(channelId [32]byte, tok
// GetChannelData is a free data retrieval call binding the contract method 0xe617208c.
//
-// Solidity: function getChannelData(bytes32 channelId) view returns((address[],address,uint64,uint64) channel, uint8 status, address[] wallets, uint256 challengeExpiry, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) lastValidState)
+// Solidity: function getChannelData(bytes32 channelId) view returns((address[],address,uint64,uint64) channel, uint8 status, address[] wallets, uint256 challengeExpiry, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) lastValidState)
func (_Custody *CustodyCaller) GetChannelData(opts *bind.CallOpts, channelId [32]byte) (struct {
Channel Channel
Status uint8
@@ -331,7 +394,7 @@ func (_Custody *CustodyCaller) GetChannelData(opts *bind.CallOpts, channelId [32
// GetChannelData is a free data retrieval call binding the contract method 0xe617208c.
//
-// Solidity: function getChannelData(bytes32 channelId) view returns((address[],address,uint64,uint64) channel, uint8 status, address[] wallets, uint256 challengeExpiry, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) lastValidState)
+// Solidity: function getChannelData(bytes32 channelId) view returns((address[],address,uint64,uint64) channel, uint8 status, address[] wallets, uint256 challengeExpiry, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) lastValidState)
func (_Custody *CustodySession) GetChannelData(channelId [32]byte) (struct {
Channel Channel
Status uint8
@@ -344,7 +407,7 @@ func (_Custody *CustodySession) GetChannelData(channelId [32]byte) (struct {
// GetChannelData is a free data retrieval call binding the contract method 0xe617208c.
//
-// Solidity: function getChannelData(bytes32 channelId) view returns((address[],address,uint64,uint64) channel, uint8 status, address[] wallets, uint256 challengeExpiry, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) lastValidState)
+// Solidity: function getChannelData(bytes32 channelId) view returns((address[],address,uint64,uint64) channel, uint8 status, address[] wallets, uint256 challengeExpiry, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) lastValidState)
func (_Custody *CustodyCallerSession) GetChannelData(channelId [32]byte) (struct {
Channel Channel
Status uint8
@@ -386,86 +449,86 @@ func (_Custody *CustodyCallerSession) GetOpenChannels(accounts []common.Address)
return _Custody.Contract.GetOpenChannels(&_Custody.CallOpts, accounts)
}
-// Challenge is a paid mutator transaction binding the contract method 0xbc7b456f.
+// Challenge is a paid mutator transaction binding the contract method 0x1474e410.
//
-// Solidity: function challenge(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs, (uint8,bytes32,bytes32) challengerSig) returns()
-func (_Custody *CustodyTransactor) Challenge(opts *bind.TransactOpts, channelId [32]byte, candidate State, proofs []State, challengerSig Signature) (*types.Transaction, error) {
+// Solidity: function challenge(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs, bytes challengerSig) returns()
+func (_Custody *CustodyTransactor) Challenge(opts *bind.TransactOpts, channelId [32]byte, candidate State, proofs []State, challengerSig []byte) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "challenge", channelId, candidate, proofs, challengerSig)
}
-// Challenge is a paid mutator transaction binding the contract method 0xbc7b456f.
+// Challenge is a paid mutator transaction binding the contract method 0x1474e410.
//
-// Solidity: function challenge(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs, (uint8,bytes32,bytes32) challengerSig) returns()
-func (_Custody *CustodySession) Challenge(channelId [32]byte, candidate State, proofs []State, challengerSig Signature) (*types.Transaction, error) {
+// Solidity: function challenge(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs, bytes challengerSig) returns()
+func (_Custody *CustodySession) Challenge(channelId [32]byte, candidate State, proofs []State, challengerSig []byte) (*types.Transaction, error) {
return _Custody.Contract.Challenge(&_Custody.TransactOpts, channelId, candidate, proofs, challengerSig)
}
-// Challenge is a paid mutator transaction binding the contract method 0xbc7b456f.
+// Challenge is a paid mutator transaction binding the contract method 0x1474e410.
//
-// Solidity: function challenge(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs, (uint8,bytes32,bytes32) challengerSig) returns()
-func (_Custody *CustodyTransactorSession) Challenge(channelId [32]byte, candidate State, proofs []State, challengerSig Signature) (*types.Transaction, error) {
+// Solidity: function challenge(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs, bytes challengerSig) returns()
+func (_Custody *CustodyTransactorSession) Challenge(channelId [32]byte, candidate State, proofs []State, challengerSig []byte) (*types.Transaction, error) {
return _Custody.Contract.Challenge(&_Custody.TransactOpts, channelId, candidate, proofs, challengerSig)
}
-// Checkpoint is a paid mutator transaction binding the contract method 0xd0cce1e8.
+// Checkpoint is a paid mutator transaction binding the contract method 0xecf668fd.
//
-// Solidity: function checkpoint(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs) returns()
+// Solidity: function checkpoint(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs) returns()
func (_Custody *CustodyTransactor) Checkpoint(opts *bind.TransactOpts, channelId [32]byte, candidate State, proofs []State) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "checkpoint", channelId, candidate, proofs)
}
-// Checkpoint is a paid mutator transaction binding the contract method 0xd0cce1e8.
+// Checkpoint is a paid mutator transaction binding the contract method 0xecf668fd.
//
-// Solidity: function checkpoint(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs) returns()
+// Solidity: function checkpoint(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs) returns()
func (_Custody *CustodySession) Checkpoint(channelId [32]byte, candidate State, proofs []State) (*types.Transaction, error) {
return _Custody.Contract.Checkpoint(&_Custody.TransactOpts, channelId, candidate, proofs)
}
-// Checkpoint is a paid mutator transaction binding the contract method 0xd0cce1e8.
+// Checkpoint is a paid mutator transaction binding the contract method 0xecf668fd.
//
-// Solidity: function checkpoint(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs) returns()
+// Solidity: function checkpoint(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs) returns()
func (_Custody *CustodyTransactorSession) Checkpoint(channelId [32]byte, candidate State, proofs []State) (*types.Transaction, error) {
return _Custody.Contract.Checkpoint(&_Custody.TransactOpts, channelId, candidate, proofs)
}
-// Close is a paid mutator transaction binding the contract method 0xde22731f.
+// Close is a paid mutator transaction binding the contract method 0x7f9ebbd7.
//
-// Solidity: function close(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] ) returns()
+// Solidity: function close(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] ) returns()
func (_Custody *CustodyTransactor) Close(opts *bind.TransactOpts, channelId [32]byte, candidate State, arg2 []State) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "close", channelId, candidate, arg2)
}
-// Close is a paid mutator transaction binding the contract method 0xde22731f.
+// Close is a paid mutator transaction binding the contract method 0x7f9ebbd7.
//
-// Solidity: function close(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] ) returns()
+// Solidity: function close(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] ) returns()
func (_Custody *CustodySession) Close(channelId [32]byte, candidate State, arg2 []State) (*types.Transaction, error) {
return _Custody.Contract.Close(&_Custody.TransactOpts, channelId, candidate, arg2)
}
-// Close is a paid mutator transaction binding the contract method 0xde22731f.
+// Close is a paid mutator transaction binding the contract method 0x7f9ebbd7.
//
-// Solidity: function close(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] ) returns()
+// Solidity: function close(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] ) returns()
func (_Custody *CustodyTransactorSession) Close(channelId [32]byte, candidate State, arg2 []State) (*types.Transaction, error) {
return _Custody.Contract.Close(&_Custody.TransactOpts, channelId, candidate, arg2)
}
-// Create is a paid mutator transaction binding the contract method 0xd37ff7b5.
+// Create is a paid mutator transaction binding the contract method 0x4a7e7798.
//
-// Solidity: function create((address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial) returns(bytes32 channelId)
+// Solidity: function create((address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial) returns(bytes32 channelId)
func (_Custody *CustodyTransactor) Create(opts *bind.TransactOpts, ch Channel, initial State) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "create", ch, initial)
}
-// Create is a paid mutator transaction binding the contract method 0xd37ff7b5.
+// Create is a paid mutator transaction binding the contract method 0x4a7e7798.
//
-// Solidity: function create((address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial) returns(bytes32 channelId)
+// Solidity: function create((address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial) returns(bytes32 channelId)
func (_Custody *CustodySession) Create(ch Channel, initial State) (*types.Transaction, error) {
return _Custody.Contract.Create(&_Custody.TransactOpts, ch, initial)
}
-// Create is a paid mutator transaction binding the contract method 0xd37ff7b5.
+// Create is a paid mutator transaction binding the contract method 0x4a7e7798.
//
-// Solidity: function create((address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial) returns(bytes32 channelId)
+// Solidity: function create((address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial) returns(bytes32 channelId)
func (_Custody *CustodyTransactorSession) Create(ch Channel, initial State) (*types.Transaction, error) {
return _Custody.Contract.Create(&_Custody.TransactOpts, ch, initial)
}
@@ -491,65 +554,65 @@ func (_Custody *CustodyTransactorSession) Deposit(account common.Address, token
return _Custody.Contract.Deposit(&_Custody.TransactOpts, account, token, amount)
}
-// DepositAndCreate is a paid mutator transaction binding the contract method 0x925bc479.
+// DepositAndCreate is a paid mutator transaction binding the contract method 0x00e2bb2c.
//
-// Solidity: function depositAndCreate(address token, uint256 amount, (address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial) payable returns(bytes32)
+// Solidity: function depositAndCreate(address token, uint256 amount, (address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial) payable returns(bytes32)
func (_Custody *CustodyTransactor) DepositAndCreate(opts *bind.TransactOpts, token common.Address, amount *big.Int, ch Channel, initial State) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "depositAndCreate", token, amount, ch, initial)
}
-// DepositAndCreate is a paid mutator transaction binding the contract method 0x925bc479.
+// DepositAndCreate is a paid mutator transaction binding the contract method 0x00e2bb2c.
//
-// Solidity: function depositAndCreate(address token, uint256 amount, (address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial) payable returns(bytes32)
+// Solidity: function depositAndCreate(address token, uint256 amount, (address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial) payable returns(bytes32)
func (_Custody *CustodySession) DepositAndCreate(token common.Address, amount *big.Int, ch Channel, initial State) (*types.Transaction, error) {
return _Custody.Contract.DepositAndCreate(&_Custody.TransactOpts, token, amount, ch, initial)
}
-// DepositAndCreate is a paid mutator transaction binding the contract method 0x925bc479.
+// DepositAndCreate is a paid mutator transaction binding the contract method 0x00e2bb2c.
//
-// Solidity: function depositAndCreate(address token, uint256 amount, (address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial) payable returns(bytes32)
+// Solidity: function depositAndCreate(address token, uint256 amount, (address[],address,uint64,uint64) ch, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial) payable returns(bytes32)
func (_Custody *CustodyTransactorSession) DepositAndCreate(token common.Address, amount *big.Int, ch Channel, initial State) (*types.Transaction, error) {
return _Custody.Contract.DepositAndCreate(&_Custody.TransactOpts, token, amount, ch, initial)
}
-// Join is a paid mutator transaction binding the contract method 0xa22b823d.
+// Join is a paid mutator transaction binding the contract method 0xbab3290a.
//
-// Solidity: function join(bytes32 channelId, uint256 index, (uint8,bytes32,bytes32) sig) returns(bytes32)
-func (_Custody *CustodyTransactor) Join(opts *bind.TransactOpts, channelId [32]byte, index *big.Int, sig Signature) (*types.Transaction, error) {
+// Solidity: function join(bytes32 channelId, uint256 index, bytes sig) returns(bytes32)
+func (_Custody *CustodyTransactor) Join(opts *bind.TransactOpts, channelId [32]byte, index *big.Int, sig []byte) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "join", channelId, index, sig)
}
-// Join is a paid mutator transaction binding the contract method 0xa22b823d.
+// Join is a paid mutator transaction binding the contract method 0xbab3290a.
//
-// Solidity: function join(bytes32 channelId, uint256 index, (uint8,bytes32,bytes32) sig) returns(bytes32)
-func (_Custody *CustodySession) Join(channelId [32]byte, index *big.Int, sig Signature) (*types.Transaction, error) {
+// Solidity: function join(bytes32 channelId, uint256 index, bytes sig) returns(bytes32)
+func (_Custody *CustodySession) Join(channelId [32]byte, index *big.Int, sig []byte) (*types.Transaction, error) {
return _Custody.Contract.Join(&_Custody.TransactOpts, channelId, index, sig)
}
-// Join is a paid mutator transaction binding the contract method 0xa22b823d.
+// Join is a paid mutator transaction binding the contract method 0xbab3290a.
//
-// Solidity: function join(bytes32 channelId, uint256 index, (uint8,bytes32,bytes32) sig) returns(bytes32)
-func (_Custody *CustodyTransactorSession) Join(channelId [32]byte, index *big.Int, sig Signature) (*types.Transaction, error) {
+// Solidity: function join(bytes32 channelId, uint256 index, bytes sig) returns(bytes32)
+func (_Custody *CustodyTransactorSession) Join(channelId [32]byte, index *big.Int, sig []byte) (*types.Transaction, error) {
return _Custody.Contract.Join(&_Custody.TransactOpts, channelId, index, sig)
}
-// Resize is a paid mutator transaction binding the contract method 0x259311c9.
+// Resize is a paid mutator transaction binding the contract method 0x183b4998.
//
-// Solidity: function resize(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs) returns()
+// Solidity: function resize(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs) returns()
func (_Custody *CustodyTransactor) Resize(opts *bind.TransactOpts, channelId [32]byte, candidate State, proofs []State) (*types.Transaction, error) {
return _Custody.contract.Transact(opts, "resize", channelId, candidate, proofs)
}
-// Resize is a paid mutator transaction binding the contract method 0x259311c9.
+// Resize is a paid mutator transaction binding the contract method 0x183b4998.
//
-// Solidity: function resize(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs) returns()
+// Solidity: function resize(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs) returns()
func (_Custody *CustodySession) Resize(channelId [32]byte, candidate State, proofs []State) (*types.Transaction, error) {
return _Custody.Contract.Resize(&_Custody.TransactOpts, channelId, candidate, proofs)
}
-// Resize is a paid mutator transaction binding the contract method 0x259311c9.
+// Resize is a paid mutator transaction binding the contract method 0x183b4998.
//
-// Solidity: function resize(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[])[] proofs) returns()
+// Solidity: function resize(bytes32 channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) candidate, (uint8,uint256,bytes,(address,address,uint256)[],bytes[])[] proofs) returns()
func (_Custody *CustodyTransactorSession) Resize(channelId [32]byte, candidate State, proofs []State) (*types.Transaction, error) {
return _Custody.Contract.Resize(&_Custody.TransactOpts, channelId, candidate, proofs)
}
@@ -650,9 +713,9 @@ type CustodyChallenged struct {
Raw types.Log // Blockchain specific contextual infos
}
-// FilterChallenged is a free log retrieval operation binding the contract event 0x2cce3a04acfb5f7911860de30611c13af2df5880b4a1f829fa7b4f2a26d03756.
+// FilterChallenged is a free log retrieval operation binding the contract event 0x44c1980976c3af1eb75b2a3b7d8c7e01f69168c0fe45dd229faf143233722e17.
//
-// Solidity: event Challenged(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) state, uint256 expiration)
+// Solidity: event Challenged(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) state, uint256 expiration)
func (_Custody *CustodyFilterer) FilterChallenged(opts *bind.FilterOpts, channelId [][32]byte) (*CustodyChallengedIterator, error) {
var channelIdRule []interface{}
@@ -667,9 +730,9 @@ func (_Custody *CustodyFilterer) FilterChallenged(opts *bind.FilterOpts, channel
return &CustodyChallengedIterator{contract: _Custody.contract, event: "Challenged", logs: logs, sub: sub}, nil
}
-// WatchChallenged is a free log subscription operation binding the contract event 0x2cce3a04acfb5f7911860de30611c13af2df5880b4a1f829fa7b4f2a26d03756.
+// WatchChallenged is a free log subscription operation binding the contract event 0x44c1980976c3af1eb75b2a3b7d8c7e01f69168c0fe45dd229faf143233722e17.
//
-// Solidity: event Challenged(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) state, uint256 expiration)
+// Solidity: event Challenged(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) state, uint256 expiration)
func (_Custody *CustodyFilterer) WatchChallenged(opts *bind.WatchOpts, sink chan<- *CustodyChallenged, channelId [][32]byte) (event.Subscription, error) {
var channelIdRule []interface{}
@@ -709,9 +772,9 @@ func (_Custody *CustodyFilterer) WatchChallenged(opts *bind.WatchOpts, sink chan
}), nil
}
-// ParseChallenged is a log parse operation binding the contract event 0x2cce3a04acfb5f7911860de30611c13af2df5880b4a1f829fa7b4f2a26d03756.
+// ParseChallenged is a log parse operation binding the contract event 0x44c1980976c3af1eb75b2a3b7d8c7e01f69168c0fe45dd229faf143233722e17.
//
-// Solidity: event Challenged(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) state, uint256 expiration)
+// Solidity: event Challenged(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) state, uint256 expiration)
func (_Custody *CustodyFilterer) ParseChallenged(log types.Log) (*CustodyChallenged, error) {
event := new(CustodyChallenged)
if err := _Custody.contract.UnpackLog(event, "Challenged", log); err != nil {
@@ -795,9 +858,9 @@ type CustodyCheckpointed struct {
Raw types.Log // Blockchain specific contextual infos
}
-// FilterCheckpointed is a free log retrieval operation binding the contract event 0xa876bb57c3d3b4b0363570fd7443e30dfe18d4b422fe9898358262d78485325d.
+// FilterCheckpointed is a free log retrieval operation binding the contract event 0x8cade4fe25d72146dc0dbe08ea2712bdcca7e2c996e2dce1e69f20e30ee1c5c3.
//
-// Solidity: event Checkpointed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) state)
+// Solidity: event Checkpointed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) state)
func (_Custody *CustodyFilterer) FilterCheckpointed(opts *bind.FilterOpts, channelId [][32]byte) (*CustodyCheckpointedIterator, error) {
var channelIdRule []interface{}
@@ -812,9 +875,9 @@ func (_Custody *CustodyFilterer) FilterCheckpointed(opts *bind.FilterOpts, chann
return &CustodyCheckpointedIterator{contract: _Custody.contract, event: "Checkpointed", logs: logs, sub: sub}, nil
}
-// WatchCheckpointed is a free log subscription operation binding the contract event 0xa876bb57c3d3b4b0363570fd7443e30dfe18d4b422fe9898358262d78485325d.
+// WatchCheckpointed is a free log subscription operation binding the contract event 0x8cade4fe25d72146dc0dbe08ea2712bdcca7e2c996e2dce1e69f20e30ee1c5c3.
//
-// Solidity: event Checkpointed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) state)
+// Solidity: event Checkpointed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) state)
func (_Custody *CustodyFilterer) WatchCheckpointed(opts *bind.WatchOpts, sink chan<- *CustodyCheckpointed, channelId [][32]byte) (event.Subscription, error) {
var channelIdRule []interface{}
@@ -854,9 +917,9 @@ func (_Custody *CustodyFilterer) WatchCheckpointed(opts *bind.WatchOpts, sink ch
}), nil
}
-// ParseCheckpointed is a log parse operation binding the contract event 0xa876bb57c3d3b4b0363570fd7443e30dfe18d4b422fe9898358262d78485325d.
+// ParseCheckpointed is a log parse operation binding the contract event 0x8cade4fe25d72146dc0dbe08ea2712bdcca7e2c996e2dce1e69f20e30ee1c5c3.
//
-// Solidity: event Checkpointed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) state)
+// Solidity: event Checkpointed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) state)
func (_Custody *CustodyFilterer) ParseCheckpointed(log types.Log) (*CustodyCheckpointed, error) {
event := new(CustodyCheckpointed)
if err := _Custody.contract.UnpackLog(event, "Checkpointed", log); err != nil {
@@ -940,9 +1003,9 @@ type CustodyClosed struct {
Raw types.Log // Blockchain specific contextual infos
}
-// FilterClosed is a free log retrieval operation binding the contract event 0x3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c910.
+// FilterClosed is a free log retrieval operation binding the contract event 0xd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a70496.
//
-// Solidity: event Closed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) finalState)
+// Solidity: event Closed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) finalState)
func (_Custody *CustodyFilterer) FilterClosed(opts *bind.FilterOpts, channelId [][32]byte) (*CustodyClosedIterator, error) {
var channelIdRule []interface{}
@@ -957,9 +1020,9 @@ func (_Custody *CustodyFilterer) FilterClosed(opts *bind.FilterOpts, channelId [
return &CustodyClosedIterator{contract: _Custody.contract, event: "Closed", logs: logs, sub: sub}, nil
}
-// WatchClosed is a free log subscription operation binding the contract event 0x3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c910.
+// WatchClosed is a free log subscription operation binding the contract event 0xd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a70496.
//
-// Solidity: event Closed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) finalState)
+// Solidity: event Closed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) finalState)
func (_Custody *CustodyFilterer) WatchClosed(opts *bind.WatchOpts, sink chan<- *CustodyClosed, channelId [][32]byte) (event.Subscription, error) {
var channelIdRule []interface{}
@@ -999,9 +1062,9 @@ func (_Custody *CustodyFilterer) WatchClosed(opts *bind.WatchOpts, sink chan<- *
}), nil
}
-// ParseClosed is a log parse operation binding the contract event 0x3646844802330633cc652490829391a0e9ddb82143a86a7e39ca148dfb05c910.
+// ParseClosed is a log parse operation binding the contract event 0xd3fa0f35ad809781b5c95d9f324b2621475e3d03254a60808cf804b663a70496.
//
-// Solidity: event Closed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) finalState)
+// Solidity: event Closed(bytes32 indexed channelId, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) finalState)
func (_Custody *CustodyFilterer) ParseClosed(log types.Log) (*CustodyClosed, error) {
event := new(CustodyClosed)
if err := _Custody.contract.UnpackLog(event, "Closed", log); err != nil {
@@ -1087,9 +1150,9 @@ type CustodyCreated struct {
Raw types.Log // Blockchain specific contextual infos
}
-// FilterCreated is a free log retrieval operation binding the contract event 0x7044488f9b947dc40d596a71992214b1050317a18ab1dced28e9d22320c39842.
+// FilterCreated is a free log retrieval operation binding the contract event 0x4dd0384c1acc40a5edb69575b4a1caa43c2c2852ef96f7ecfc4a6705ddb8ccc7.
//
-// Solidity: event Created(bytes32 indexed channelId, address indexed wallet, (address[],address,uint64,uint64) channel, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial)
+// Solidity: event Created(bytes32 indexed channelId, address indexed wallet, (address[],address,uint64,uint64) channel, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial)
func (_Custody *CustodyFilterer) FilterCreated(opts *bind.FilterOpts, channelId [][32]byte, wallet []common.Address) (*CustodyCreatedIterator, error) {
var channelIdRule []interface{}
@@ -1108,9 +1171,9 @@ func (_Custody *CustodyFilterer) FilterCreated(opts *bind.FilterOpts, channelId
return &CustodyCreatedIterator{contract: _Custody.contract, event: "Created", logs: logs, sub: sub}, nil
}
-// WatchCreated is a free log subscription operation binding the contract event 0x7044488f9b947dc40d596a71992214b1050317a18ab1dced28e9d22320c39842.
+// WatchCreated is a free log subscription operation binding the contract event 0x4dd0384c1acc40a5edb69575b4a1caa43c2c2852ef96f7ecfc4a6705ddb8ccc7.
//
-// Solidity: event Created(bytes32 indexed channelId, address indexed wallet, (address[],address,uint64,uint64) channel, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial)
+// Solidity: event Created(bytes32 indexed channelId, address indexed wallet, (address[],address,uint64,uint64) channel, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial)
func (_Custody *CustodyFilterer) WatchCreated(opts *bind.WatchOpts, sink chan<- *CustodyCreated, channelId [][32]byte, wallet []common.Address) (event.Subscription, error) {
var channelIdRule []interface{}
@@ -1154,9 +1217,9 @@ func (_Custody *CustodyFilterer) WatchCreated(opts *bind.WatchOpts, sink chan<-
}), nil
}
-// ParseCreated is a log parse operation binding the contract event 0x7044488f9b947dc40d596a71992214b1050317a18ab1dced28e9d22320c39842.
+// ParseCreated is a log parse operation binding the contract event 0x4dd0384c1acc40a5edb69575b4a1caa43c2c2852ef96f7ecfc4a6705ddb8ccc7.
//
-// Solidity: event Created(bytes32 indexed channelId, address indexed wallet, (address[],address,uint64,uint64) channel, (uint8,uint256,bytes,(address,address,uint256)[],(uint8,bytes32,bytes32)[]) initial)
+// Solidity: event Created(bytes32 indexed channelId, address indexed wallet, (address[],address,uint64,uint64) channel, (uint8,uint256,bytes,(address,address,uint256)[],bytes[]) initial)
func (_Custody *CustodyFilterer) ParseCreated(log types.Log) (*CustodyCreated, error) {
event := new(CustodyCreated)
if err := _Custody.contract.UnpackLog(event, "Created", log); err != nil {
@@ -1320,6 +1383,139 @@ func (_Custody *CustodyFilterer) ParseDeposited(log types.Log) (*CustodyDeposite
return event, nil
}
+// CustodyEIP712DomainChangedIterator is returned from FilterEIP712DomainChanged and is used to iterate over the raw logs and unpacked data for EIP712DomainChanged events raised by the Custody contract.
+type CustodyEIP712DomainChangedIterator struct {
+ Event *CustodyEIP712DomainChanged // Event containing the contract specifics and raw log
+
+ contract *bind.BoundContract // Generic contract to use for unpacking event data
+ event string // Event name to use for unpacking event data
+
+ logs chan types.Log // Log channel receiving the found contract events
+ sub ethereum.Subscription // Subscription for errors, completion and termination
+ done bool // Whether the subscription completed delivering logs
+ fail error // Occurred error to stop iteration
+}
+
+// Next advances the iterator to the subsequent event, returning whether there
+// are any more events found. In case of a retrieval or parsing error, false is
+// returned and Error() can be queried for the exact failure.
+func (it *CustodyEIP712DomainChangedIterator) Next() bool {
+ // If the iterator failed, stop iterating
+ if it.fail != nil {
+ return false
+ }
+ // If the iterator completed, deliver directly whatever's available
+ if it.done {
+ select {
+ case log := <-it.logs:
+ it.Event = new(CustodyEIP712DomainChanged)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ default:
+ return false
+ }
+ }
+ // Iterator still in progress, wait for either a data or an error event
+ select {
+ case log := <-it.logs:
+ it.Event = new(CustodyEIP712DomainChanged)
+ if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil {
+ it.fail = err
+ return false
+ }
+ it.Event.Raw = log
+ return true
+
+ case err := <-it.sub.Err():
+ it.done = true
+ it.fail = err
+ return it.Next()
+ }
+}
+
+// Error returns any retrieval or parsing error occurred during filtering.
+func (it *CustodyEIP712DomainChangedIterator) Error() error {
+ return it.fail
+}
+
+// Close terminates the iteration process, releasing any pending underlying
+// resources.
+func (it *CustodyEIP712DomainChangedIterator) Close() error {
+ it.sub.Unsubscribe()
+ return nil
+}
+
+// CustodyEIP712DomainChanged represents a EIP712DomainChanged event raised by the Custody contract.
+type CustodyEIP712DomainChanged struct {
+ Raw types.Log // Blockchain specific contextual infos
+}
+
+// FilterEIP712DomainChanged is a free log retrieval operation binding the contract event 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31.
+//
+// Solidity: event EIP712DomainChanged()
+func (_Custody *CustodyFilterer) FilterEIP712DomainChanged(opts *bind.FilterOpts) (*CustodyEIP712DomainChangedIterator, error) {
+
+ logs, sub, err := _Custody.contract.FilterLogs(opts, "EIP712DomainChanged")
+ if err != nil {
+ return nil, err
+ }
+ return &CustodyEIP712DomainChangedIterator{contract: _Custody.contract, event: "EIP712DomainChanged", logs: logs, sub: sub}, nil
+}
+
+// WatchEIP712DomainChanged is a free log subscription operation binding the contract event 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31.
+//
+// Solidity: event EIP712DomainChanged()
+func (_Custody *CustodyFilterer) WatchEIP712DomainChanged(opts *bind.WatchOpts, sink chan<- *CustodyEIP712DomainChanged) (event.Subscription, error) {
+
+ logs, sub, err := _Custody.contract.WatchLogs(opts, "EIP712DomainChanged")
+ if err != nil {
+ return nil, err
+ }
+ return event.NewSubscription(func(quit <-chan struct{}) error {
+ defer sub.Unsubscribe()
+ for {
+ select {
+ case log := <-logs:
+ // New log arrived, parse the event and forward to the user
+ event := new(CustodyEIP712DomainChanged)
+ if err := _Custody.contract.UnpackLog(event, "EIP712DomainChanged", log); err != nil {
+ return err
+ }
+ event.Raw = log
+
+ select {
+ case sink <- event:
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ case err := <-sub.Err():
+ return err
+ case <-quit:
+ return nil
+ }
+ }
+ }), nil
+}
+
+// ParseEIP712DomainChanged is a log parse operation binding the contract event 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31.
+//
+// Solidity: event EIP712DomainChanged()
+func (_Custody *CustodyFilterer) ParseEIP712DomainChanged(log types.Log) (*CustodyEIP712DomainChanged, error) {
+ event := new(CustodyEIP712DomainChanged)
+ if err := _Custody.contract.UnpackLog(event, "EIP712DomainChanged", log); err != nil {
+ return nil, err
+ }
+ event.Raw = log
+ return event, nil
+}
+
// CustodyJoinedIterator is returned from FilterJoined and is used to iterate over the raw logs and unpacked data for Joined events raised by the Custody contract.
type CustodyJoinedIterator struct {
Event *CustodyJoined // Event containing the contract specifics and raw log
diff --git a/clearnode/nitrolite/signature.go b/clearnode/nitrolite/signature.go
index 124910294..beb75ff2c 100644
--- a/clearnode/nitrolite/signature.go
+++ b/clearnode/nitrolite/signature.go
@@ -2,49 +2,95 @@ package nitrolite
import (
"crypto/ecdsa"
+ "encoding/json"
"fmt"
"github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
)
+type Signature []byte
+
+func (s Signature) MarshalJSON() ([]byte, error) {
+ return json.Marshal(hexutil.Encode(s))
+}
+
+func (s *Signature) UnmarshalJSON(data []byte) error {
+ var hexStr string
+ if err := json.Unmarshal(data, &hexStr); err != nil {
+ return err
+ }
+ decoded, err := hexutil.Decode(hexStr)
+ if err != nil {
+ return err
+ }
+ *s = decoded
+ return nil
+}
+
+func (s Signature) String() string {
+ return hexutil.Encode(s)
+}
+
+func SignaturesToStrings(signatures []Signature) []string {
+ strs := make([]string, len(signatures))
+ for i, sig := range signatures {
+ strs[i] = sig.String()
+ }
+ return strs
+}
+
+func SignaturesFromStrings(strs []string) ([]Signature, error) {
+ signatures := make([]Signature, len(strs))
+ for i, str := range strs {
+ sig, err := hexutil.Decode(str)
+ if err != nil {
+ return nil, fmt.Errorf("failed to decode signature %d (%s): %w", i, str, err)
+ }
+ signatures[i] = sig
+ }
+ return signatures, nil
+}
+
// Sign hashes the provided data using Keccak256 and signs it with the given private key.
func Sign(data []byte, privateKey *ecdsa.PrivateKey) (Signature, error) {
if privateKey == nil {
- return Signature{}, fmt.Errorf("private key is nil")
+ return nil, fmt.Errorf("private key is nil")
}
dataHash := crypto.Keccak256Hash(data)
signature, err := crypto.Sign(dataHash.Bytes(), privateKey)
if err != nil {
- return Signature{}, fmt.Errorf("failed to sign data: %w", err)
+ return nil, fmt.Errorf("failed to sign data: %w", err)
}
if len(signature) != 65 {
- return Signature{}, fmt.Errorf("invalid signature length: got %d, want 65", len(signature))
+ return nil, fmt.Errorf("invalid signature length: got %d, want 65", len(signature))
}
- var sig Signature
- copy(sig.R[:], signature[:32])
- copy(sig.S[:], signature[32:64])
- sig.V = signature[64] + 27
+ // This step is necessary to remain compatible with the ecrecover precompile
+ if signature[64] < 27 {
+ signature[64] += 27
+ }
- return sig, nil
+ return signature, nil
}
// Verify checks if the signature on the provided data was created by the given address.
func Verify(data []byte, sig Signature, address common.Address) (bool, error) {
dataHash := crypto.Keccak256Hash(data)
- signature := make([]byte, 65)
- copy(signature[0:32], sig.R[:])
- copy(signature[32:64], sig.S[:])
+ // Create a copy of the signature to avoid modifying the original
+ sigToVerify := make(Signature, len(sig))
+ copy(sigToVerify, sig)
- if sig.V >= 27 {
- signature[64] = sig.V - 27
+ // Ensure the signature is in the correct format
+ if sigToVerify[64] >= 27 {
+ sigToVerify[64] -= 27
}
- pubKeyRaw, err := crypto.Ecrecover(dataHash.Bytes(), signature)
+ pubKeyRaw, err := crypto.Ecrecover(dataHash.Bytes(), sigToVerify)
if err != nil {
return false, fmt.Errorf("failed to recover public key: %w", err)
}
diff --git a/clearnode/nitrolite/signature_test.go b/clearnode/nitrolite/signature_test.go
index 17a83812d..716662437 100644
--- a/clearnode/nitrolite/signature_test.go
+++ b/clearnode/nitrolite/signature_test.go
@@ -71,8 +71,8 @@ func TestVerifyInvalidSignature(t *testing.T) {
t.Fatalf("failed to sign data: %v", err)
}
- // Tamper with the signature (flip a bit in R).
- sig.R[0] ^= 0xff
+ // Tamper with the signature (flip some bit).
+ sig[0] ^= 0xff
// Use the original public address.
publicAddress := crypto.PubkeyToAddress(privateKey.PublicKey)
diff --git a/clearnode/nitrolite/state.go b/clearnode/nitrolite/state.go
index c36223f11..89ec62df1 100644
--- a/clearnode/nitrolite/state.go
+++ b/clearnode/nitrolite/state.go
@@ -16,8 +16,8 @@ const (
IntentFINALIZE Intent = 3
)
-// EncodeState encodes channel state into a byte array using channelID, intent, version, state data, and allocations.
-func EncodeState(channelID common.Hash, intent Intent, version *big.Int, stateData []byte, allocations []Allocation) ([]byte, error) {
+// PackState encodes channel id and state into a byte array
+func PackState(channelID common.Hash, intent Intent, version *big.Int, stateData []byte, allocations []Allocation) ([]byte, error) {
allocationType, err := abi.NewType("tuple[]", "", []abi.ArgumentMarshaling{
{Name: "destination", Type: "address"},
{Name: "token", Type: "address"},
diff --git a/clearnode/rpc.go b/clearnode/rpc.go
index 9f92c684f..9e373853c 100644
--- a/clearnode/rpc.go
+++ b/clearnode/rpc.go
@@ -9,10 +9,10 @@ import (
// RPCMessage represents a complete message in the RPC protocol, including data and signatures
type RPCMessage struct {
- Req *RPCData `json:"req,omitempty" validate:"required_without=Res,excluded_with=Res"`
- Res *RPCData `json:"res,omitempty" validate:"required_without=Req,excluded_with=Req"`
- AppSessionID string `json:"sid,omitempty"`
- Sig []string `json:"sig"`
+ Req *RPCData `json:"req,omitempty" validate:"required_without=Res,excluded_with=Res"`
+ Res *RPCData `json:"res,omitempty" validate:"required_without=Req,excluded_with=Req"`
+ AppSessionID string `json:"sid,omitempty"`
+ Sig []Signature `json:"sig"`
}
// ParseRPCMessage parses a JSON string into an RPCMessage
@@ -100,7 +100,7 @@ func CreateResponse(id uint64, method string, responseParams []any) *RPCMessage
Params: responseParams,
Timestamp: uint64(time.Now().UnixMilli()),
},
- Sig: []string{},
+ Sig: []Signature{},
}
}
diff --git a/clearnode/rpc_node.go b/clearnode/rpc_node.go
index 2b643d500..81256de77 100644
--- a/clearnode/rpc_node.go
+++ b/clearnode/rpc_node.go
@@ -9,7 +9,6 @@ import (
"sync"
"time"
- "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/go-playground/validator/v10"
"github.com/google/uuid"
"github.com/gorilla/websocket"
@@ -168,10 +167,10 @@ read_loop:
}
}
- var msg RPCMessage
+ msg := RPCMessage{Req: &RPCData{}}
if err := json.Unmarshal(messageBytes, &msg); err != nil {
n.logger.Debug("invalid message format", "error", err, "message", string(messageBytes))
- n.sendErrorResponse(rpcConn, 0, "invalid message format")
+ n.sendErrorResponse(rpcConn, msg.Req.RequestID, "invalid message format")
continue
}
@@ -355,7 +354,7 @@ func prepareRawRPCResponse(signer *Signer, data *RPCData) ([]byte, error) {
responseMessage := &RPCMessage{
Res: data,
- Sig: []string{hexutil.Encode(signature)},
+ Sig: []Signature{signature},
}
resMessageBytes, err := json.Marshal(responseMessage)
if err != nil {
diff --git a/clearnode/rpc_node_test.go b/clearnode/rpc_node_test.go
index 4d21d4011..bd013069c 100644
--- a/clearnode/rpc_node_test.go
+++ b/clearnode/rpc_node_test.go
@@ -191,7 +191,7 @@ func TestRPCNode(t *testing.T) {
reqMsg := &RPCMessage{
Req: reqData,
- Sig: []string{},
+ Sig: []Signature{},
}
// Send request
diff --git a/clearnode/rpc_router.go b/clearnode/rpc_router.go
index 2bfc45cc9..a21772862 100644
--- a/clearnode/rpc_router.go
+++ b/clearnode/rpc_router.go
@@ -204,15 +204,15 @@ func (r *RPCRouter) MetricsMiddleware(c *RPCContext) {
}
type RPCEntry struct {
- ID uint `json:"id"`
- Sender string `json:"sender"`
- ReqID uint64 `json:"req_id"`
- Method string `json:"method"`
- Params string `json:"params"`
- Timestamp uint64 `json:"timestamp"`
- ReqSig []string `json:"req_sig"`
- Result string `json:"response"`
- ResSig []string `json:"res_sig"`
+ ID uint `json:"id"`
+ Sender string `json:"sender"`
+ ReqID uint64 `json:"req_id"`
+ Method string `json:"method"`
+ Params string `json:"params"`
+ Timestamp uint64 `json:"timestamp"`
+ ReqSig []Signature `json:"req_sig"`
+ Result string `json:"response"`
+ ResSig []Signature `json:"res_sig"`
}
func (r *RPCRouter) HistoryMiddleware(c *RPCContext) {
diff --git a/clearnode/rpc_router_auth.go b/clearnode/rpc_router_auth.go
index 0f92cdb09..16fd29e9b 100644
--- a/clearnode/rpc_router_auth.go
+++ b/clearnode/rpc_router_auth.go
@@ -244,7 +244,7 @@ func (r *RPCRouter) handleAuthJWTVerify(ctx context.Context, authParams AuthVeri
}
// handleAuthJWTVerify verifies the challenge signature and returns the policy, response data and error.
-func (r *RPCRouter) handleAuthSigVerify(ctx context.Context, sig string, authParams AuthVerifyParams) (*Policy, any, error) {
+func (r *RPCRouter) handleAuthSigVerify(ctx context.Context, sig Signature, authParams AuthVerifyParams) (*Policy, any, error) {
logger := LoggerFromContext(ctx)
challenge, err := r.AuthManager.GetChallenge(authParams.Challenge)
diff --git a/clearnode/rpc_router_private.go b/clearnode/rpc_router_private.go
index 262d40990..ca2eb689f 100644
--- a/clearnode/rpc_router_private.go
+++ b/clearnode/rpc_router_private.go
@@ -3,6 +3,7 @@ package main
import (
"fmt"
+ "github.com/erc7824/nitrolite/clearnode/nitrolite"
"github.com/ethereum/go-ethereum/common"
"github.com/shopspring/decimal"
"gorm.io/gorm"
@@ -84,7 +85,6 @@ type ResizeChannelResponse struct {
Intent uint8 `json:"intent"`
Version uint64 `json:"version"`
Allocations []Allocation `json:"allocations"`
- StateHash string `json:"state_hash"`
Signature Signature `json:"server_signature"`
}
@@ -105,7 +105,6 @@ type CloseChannelResponse struct {
Version uint64 `json:"version"`
StateData string `json:"state_data"`
FinalAllocations []Allocation `json:"allocations"`
- StateHash string `json:"state_hash"`
Signature Signature `json:"server_signature"`
}
@@ -125,12 +124,6 @@ type ChannelResponse struct {
UpdatedAt string `json:"updated_at"`
}
-type Signature struct {
- V uint8 `json:"v,string"`
- R string `json:"r"`
- S string `json:"s"`
-}
-
type Balance struct {
Asset string `json:"asset"`
Amount decimal.Decimal `json:"amount"`
@@ -579,6 +572,20 @@ func (r *RPCRouter) HandleGetRPCHistory(c *RPCContext) {
response := make([]RPCEntry, 0, len(rpcHistory))
for _, record := range rpcHistory {
+ reqSigs, err := nitrolite.SignaturesFromStrings(record.ReqSig)
+ if err != nil {
+ logger.Error("failed to decode request signature", "error", err, "recordID", record.ID)
+ c.Fail(err, "failed to decode request signature")
+ return
+ }
+
+ resSigs, err := nitrolite.SignaturesFromStrings(record.ResSig)
+ if err != nil {
+ logger.Error("failed to decode response signature", "error", err, "recordID", record.ID)
+ c.Fail(err, "failed to decode response signature")
+ return
+ }
+
response = append(response, RPCEntry{
ID: record.ID,
Sender: record.Sender,
@@ -586,8 +593,8 @@ func (r *RPCRouter) HandleGetRPCHistory(c *RPCContext) {
Method: record.Method,
Params: string(record.Params),
Timestamp: record.Timestamp,
- ReqSig: record.ReqSig,
- ResSig: record.ResSig,
+ ReqSig: reqSigs,
+ ResSig: resSigs,
Result: string(record.Response),
})
}
diff --git a/clearnode/rpc_router_private_test.go b/clearnode/rpc_router_private_test.go
index 48c854cd5..589b0b6f5 100644
--- a/clearnode/rpc_router_private_test.go
+++ b/clearnode/rpc_router_private_test.go
@@ -8,7 +8,6 @@ import (
"testing"
"time"
- "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/shopspring/decimal"
"github.com/stretchr/testify/assert"
@@ -25,10 +24,10 @@ func createSignedRPCContext(id int, method string, params any, signers ...Signer
rawReq, _ := json.Marshal(ctx.Message.Req)
ctx.Message.Req.rawBytes = rawReq
- ctx.Message.Sig = make([]string, 0, len(signers))
+ ctx.Message.Sig = make([]Signature, 0, len(signers))
for _, signer := range signers {
sigBytes, _ := signer.Sign(rawReq)
- ctx.Message.Sig = append(ctx.Message.Sig, hexutil.Encode(sigBytes))
+ ctx.Message.Sig = append(ctx.Message.Sig, sigBytes)
}
return ctx
@@ -65,18 +64,27 @@ func TestRPCRouterHandleGetRPCHistory(t *testing.T) {
baseTime := uint64(time.Now().Unix())
// Create 11 test records for pagination testing
records := []RPCRecord{
- {Sender: userAddress, ReqID: 1, Method: "ping", Params: []byte(`[null]`), Timestamp: baseTime - 10, ReqSig: []string{"sig1"}, Response: []byte(`{"res":[1,"pong",[],1621234567890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 2, Method: "get_config", Params: []byte(`[]`), Timestamp: baseTime - 9, ReqSig: []string{"sig2"}, Response: []byte(`{"res":[2,"get_config",[{"broker_address":"0xBroker"}],1621234597890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 3, Method: "get_channels", Params: []byte(fmt.Sprintf(`[{"participant":"%s"}]`, userAddress)), Timestamp: baseTime - 8, ReqSig: []string{"sig3"}, Response: []byte(`{"res":[3,"get_channels",[[]],1621234627890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 4, Method: "transfer", Params: []byte(`[{"destination":"0xDest","allocations":[{"asset":"USDC","amount":"100"}]}]`), Timestamp: baseTime - 7, ReqSig: []string{"sig4"}, Response: []byte(`{"res":[4,"transfer",[],1621234657890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 5, Method: "get_ledger_balances", Params: []byte(`[]`), Timestamp: baseTime - 6, ReqSig: []string{"sig5"}, Response: []byte(`{"res":[5,"get_ledger_balances",[],1621234687890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 6, Method: "create_application", Params: []byte(`[{"definition":{"protocol":"test"}}]`), Timestamp: baseTime - 5, ReqSig: []string{"sig6"}, Response: []byte(`{"res":[6,"create_application",[],1621234717890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 7, Method: "submit_app_state", Params: []byte(`[{"app_session_id":"123"}]`), Timestamp: baseTime - 4, ReqSig: []string{"sig7"}, Response: []byte(`{"res":[7,"submit_app_state",[],1621234747890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 8, Method: "close_application", Params: []byte(`[{"app_session_id":"123"}]`), Timestamp: baseTime - 3, ReqSig: []string{"sig8"}, Response: []byte(`{"res":[8,"close_application",[],1621234777890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 9, Method: "resize_channel", Params: []byte(`[{"channel_id":"ch123"}]`), Timestamp: baseTime - 2, ReqSig: []string{"sig9"}, Response: []byte(`{"res":[9,"resize_channel",[],1621234807890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 10, Method: "close_channel", Params: []byte(`[{"channel_id":"ch123"}]`), Timestamp: baseTime - 1, ReqSig: []string{"sig10"}, Response: []byte(`{"res":[10,"close_channel",[],1621234837890]}`), ResSig: []string{}},
- {Sender: userAddress, ReqID: 11, Method: "get_user_tag", Params: []byte(`[]`), Timestamp: baseTime, ReqSig: []string{"sig11"}, Response: []byte(`{"res":[11,"get_user_tag",[],1621234867890]}`), ResSig: []string{}},
- {Sender: "0xOtherParticipant", ReqID: 12, Method: "ping", Params: []byte(`[null]`), Timestamp: baseTime + 1, ReqSig: []string{"sig12"}, Response: []byte(`{"res":[12,"pong",[],1621234897890]}`)},
+ {Sender: userAddress, Method: "ping", Params: []byte(`[null]`), Response: []byte(`{"res":[1,"pong",[],1621234567890]}`)},
+ {Sender: userAddress, Method: "get_config", Params: []byte(`[]`), Response: []byte(`{"res":[2,"get_config",[{"broker_address":"0xBroker"}],1621234597890]}`)},
+ {Sender: userAddress, Method: "get_channels", Params: []byte(fmt.Sprintf(`[{"participant":"%s"}]`, userAddress)), Response: []byte(`{"res":[3,"get_channels",[[]],1621234627890]}`)},
+ {Sender: userAddress, Method: "transfer", Params: []byte(`[{"destination":"0xDest","allocations":[{"asset":"USDC","amount":"100"}]}]`), Response: []byte(`{"res":[4,"transfer",[],1621234657890]}`)},
+ {Sender: userAddress, Method: "get_ledger_balances", Params: []byte(`[]`), Response: []byte(`{"res":[5,"get_ledger_balances",[],1621234687890]}`)},
+ {Sender: userAddress, Method: "create_application", Params: []byte(`[{"definition":{"protocol":"test"}}]`), ReqSig: []string{"0x0006"}, Response: []byte(`{"res":[6,"create_application",[],1621234717890]}`)},
+ {Sender: userAddress, Method: "submit_app_state", Params: []byte(`[{"app_session_id":"123"}]`), Response: []byte(`{"res":[7,"submit_app_state",[],1621234747890]}`)},
+ {Sender: userAddress, Method: "close_application", Params: []byte(`[{"app_session_id":"123"}]`), Response: []byte(`{"res":[8,"close_application",[],1621234777890]}`)},
+ {Sender: userAddress, Method: "resize_channel", Params: []byte(`[{"channel_id":"ch123"}]`), Response: []byte(`{"res":[9,"resize_channel",[],1621234807890]}`)},
+ {Sender: userAddress, Method: "close_channel", Params: []byte(`[{"channel_id":"ch123"}]`), Response: []byte(`{"res":[10,"close_channel",[],1621234837890]}`)},
+ {Sender: userAddress, Method: "get_user_tag", Params: []byte(`[]`), Response: []byte(`{"res":[11,"get_user_tag",[],1621234867890]}`)},
+ {Sender: "0xOtherParticipant", Method: "ping", Params: []byte(`[null]`), Response: []byte(`{"res":[12,"pong",[],1621234897890]}`)},
+ }
+
+ numOfTestRecords := len(records)
+
+ for i := range records {
+ records[i].ReqID = uint64(i + 1)
+ records[i].Timestamp = baseTime - uint64(numOfTestRecords-i)
+ records[i].ReqSig = []string{fmt.Sprintf("0x%04X", i+1)}
+ records[i].ResSig = []string{}
}
require.NoError(t, router.DB.Create(records).Error)
@@ -350,7 +358,7 @@ func TestRPCRouterHandleTransfer(t *testing.T) {
require.NoError(t, GetWalletLedger(db, senderAddr).Record(senderAccountID, "usdc", decimal.NewFromInt(1000)))
require.NoError(t, GetWalletLedger(db, senderAddr).Record(senderAccountID, "eth", decimal.NewFromInt(5)))
- // Setup user tag for receipient
+ // Setup user tag for recipient
recipientTag, err := GenerateOrRetrieveUserTag(db, recipientAddr.Hex())
require.NoError(t, err)
diff --git a/clearnode/rpc_router_public_test.go b/clearnode/rpc_router_public_test.go
index 38739cda5..46ebefbdb 100644
--- a/clearnode/rpc_router_public_test.go
+++ b/clearnode/rpc_router_public_test.go
@@ -28,7 +28,7 @@ func createRPCContext(id int, method string, params any) *RPCContext {
Params: rpcParams,
Timestamp: uint64(time.Now().Unix()),
},
- Sig: []string{"dummy-signature"},
+ Sig: []Signature{Signature([]byte("dummy-signature"))},
},
}
}
diff --git a/clearnode/rpc_store.go b/clearnode/rpc_store.go
index ce3db3c78..de65cf490 100644
--- a/clearnode/rpc_store.go
+++ b/clearnode/rpc_store.go
@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
+ "github.com/erc7824/nitrolite/clearnode/nitrolite"
"github.com/lib/pq"
"gorm.io/gorm"
)
@@ -36,7 +37,7 @@ func NewRPCStore(db *gorm.DB) *RPCStore {
}
// StoreMessage stores an RPC message in the database
-func (s *RPCStore) StoreMessage(sender string, req *RPCData, reqSig []string, resBytes []byte, resSig []string) error {
+func (s *RPCStore) StoreMessage(sender string, req *RPCData, reqSigs []Signature, resBytes []byte, resSigs []Signature) error {
paramsBytes, err := json.Marshal(req.Params)
if err != nil {
return err
@@ -48,8 +49,8 @@ func (s *RPCStore) StoreMessage(sender string, req *RPCData, reqSig []string, re
Method: req.Method,
Params: paramsBytes,
Response: resBytes,
- ReqSig: reqSig,
- ResSig: resSig,
+ ReqSig: nitrolite.SignaturesToStrings(reqSigs),
+ ResSig: nitrolite.SignaturesToStrings(resSigs),
Timestamp: req.Timestamp,
}
diff --git a/clearnode/rpc_store_test.go b/clearnode/rpc_store_test.go
index cb89eb259..9fc27178d 100644
--- a/clearnode/rpc_store_test.go
+++ b/clearnode/rpc_store_test.go
@@ -5,6 +5,8 @@ import (
"testing"
"time"
+ "github.com/erc7824/nitrolite/clearnode/nitrolite"
+ "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -39,9 +41,9 @@ func TestRPCStoreStoreMessage(t *testing.T) {
"key1": "value1",
"key2": 42,
}
- reqSig := []string{"sig1", "sig2"}
+ reqSig := []Signature{Signature(hexutil.MustDecode("0x1234")), Signature(hexutil.MustDecode("0x4567"))}
resBytes := []byte(`{"result": "ok"}`)
- resSig := []string{"resSig1"}
+ resSig := []Signature{Signature(hexutil.MustDecode("0x4321"))}
// Create RPCData
req := &RPCData{
@@ -69,8 +71,9 @@ func TestRPCStoreStoreMessage(t *testing.T) {
assert.Equal(t, reqID, record.ReqID)
assert.Equal(t, method, record.Method)
assert.Equal(t, timestamp, record.Timestamp)
- assert.ElementsMatch(t, reqSig, record.ReqSig)
- assert.ElementsMatch(t, resSig, record.ResSig)
+
+ assert.ElementsMatch(t, nitrolite.SignaturesToStrings(reqSig), record.ReqSig)
+ assert.ElementsMatch(t, nitrolite.SignaturesToStrings(resSig), record.ResSig)
assert.Equal(t, resBytes, record.Response)
// Verify params were stored correctly
@@ -83,7 +86,7 @@ func TestRPCStoreStoreMessage(t *testing.T) {
// Extract the first element which is our original map
storedParams := storedParamsArray[0]
assert.Equal(t, "value1", storedParams["key1"])
- assert.Equal(t, float64(42), storedParams["key2"]) // JSON unmarshals numbers as float64
+ assert.Equal(t, float64(42), storedParams["key2"]) // JSON unmarshal numbers as float64
}
// TestRPCStoreStoreMessageError tests error handling for StoreMessage
@@ -103,11 +106,11 @@ func TestRPCStoreStoreMessageError(t *testing.T) {
Params: []any{make(chan int)}, // Channels cannot be marshalled to JSON
Timestamp: uint64(time.Now().Unix()),
}
- reqSig := []string{"sig1"}
+ reqSig := []Signature{Signature([]byte("sig1"))}
resBytes := []byte(`{"result": "ok"}`)
- resSig := []string{"resSig1"}
+ resSig := []Signature{Signature([]byte("resSig1"))}
- // Attempt to store the message, should fail due to unmarshalable params
+ // Attempt to store the message, should fail due to unmarshal-able params
err := store.StoreMessage(sender, req, reqSig, resBytes, resSig)
assert.Error(t, err)
assert.Contains(t, err.Error(), "json")
diff --git a/clearnode/rpc_test.go b/clearnode/rpc_test.go
index 25ee6ee82..2320679f1 100644
--- a/clearnode/rpc_test.go
+++ b/clearnode/rpc_test.go
@@ -16,7 +16,7 @@ func TestRPCMessageValidate(t *testing.T) {
Params: []any{"param1", 2},
Timestamp: uint64(time.Now().Unix()),
},
- Sig: []string{"0x1234567890abcdef"},
+ Sig: []Signature{Signature([]byte("0x1234567890abcdef"))},
}
if err := validate.Struct(rpcMsg); err != nil {
diff --git a/clearnode/signer.go b/clearnode/signer.go
index 26a297c80..2dfab1db1 100644
--- a/clearnode/signer.go
+++ b/clearnode/signer.go
@@ -8,11 +8,12 @@ import (
"github.com/erc7824/nitrolite/clearnode/nitrolite"
"github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)
+type Signature = nitrolite.Signature
+
// Allowance represents allowances for connection
type Allowance struct {
Asset string `json:"asset"`
@@ -39,33 +40,8 @@ func NewSigner(privateKeyHex string) (*Signer, error) {
}
// Sign creates an ECDSA signature for the provided data
-func (s *Signer) Sign(data []byte) ([]byte, error) {
- sig, err := nitrolite.Sign(data, s.privateKey)
- if err != nil {
- return nil, err
- }
-
- signature := make([]byte, 65)
- copy(signature[0:32], sig.R[:])
- copy(signature[32:64], sig.S[:])
-
- if sig.V >= 27 {
- signature[64] = sig.V - 27
- }
- return signature, nil
-}
-
-// NitroSign creates a signature for the provided state in nitrolite.Signature format
-func (s *Signer) NitroSign(encodedState []byte) (nitrolite.Signature, error) {
- sig, err := nitrolite.Sign(encodedState, s.privateKey)
- if err != nil {
- return nitrolite.Signature{}, fmt.Errorf("failed to sign encoded state: %w", err)
- }
- return nitrolite.Signature{
- V: sig.V,
- R: sig.R,
- S: sig.S,
- }, nil
+func (s *Signer) Sign(data []byte) (Signature, error) {
+ return nitrolite.Sign(data, s.privateKey)
}
// GetPublicKey returns the public key associated with the signer
@@ -84,11 +60,7 @@ func (s *Signer) GetAddress() common.Address {
}
// RecoverAddress takes the original message and its hex-encoded signature, and returns the address
-func RecoverAddress(message []byte, signatureHex string) (string, error) {
- sig, err := hexutil.Decode(signatureHex)
- if err != nil {
- return "", fmt.Errorf("invalid signature hex: %w", err)
- }
+func RecoverAddress(message []byte, sig Signature) (string, error) {
if len(sig) != 65 {
return "", fmt.Errorf("invalid signature length: got %d, want 65", len(sig))
}
@@ -117,7 +89,7 @@ func RecoverAddressFromEip712Signature(
scope string,
application string,
expire string,
- signatureHex string) (string, error) {
+ sig Signature) (string, error) {
convertedAllowances := convertAllowances(allowances)
typedData := apitypes.TypedData{
@@ -159,18 +131,12 @@ func RecoverAddressFromEip712Signature(
return "", err
}
- // 2. Example signature
- sig, err := hexutil.Decode(signatureHex)
- if err != nil {
- return "", err
- }
-
- // 3. Fix V if needed (Ethereum uses 27/28, go-ethereum expects 0/1)
+ // 2. Fix V if needed (Ethereum uses 27/28, go-ethereum expects 0/1)
if sig[64] >= 27 {
sig[64] -= 27
}
- // 4. Recover public key
+ // 3. Recover public key
pubKey, err := crypto.SigToPub(typedDataHash, sig)
if err != nil {
return "", err
diff --git a/clearnode/signer_test.go b/clearnode/signer_test.go
index 9d5f7b53f..15f6a68f6 100644
--- a/clearnode/signer_test.go
+++ b/clearnode/signer_test.go
@@ -1,8 +1,10 @@
package main
import (
- "github.com/stretchr/testify/assert"
"testing"
+
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/stretchr/testify/assert"
)
func TestEIPSignature(t *testing.T) {
@@ -12,6 +14,9 @@ func TestEIPSignature(t *testing.T) {
Amount: "0",
},
}
+ signature, err := hexutil.Decode("0xe758880bc3d75e9433e0f50c9b40712b0bcf90f437a8c42ba6f8a5a3d144a5ce4b10b020c4eb728323daad49c4cc6329eaa45e3ea88c95d76e55b982f6a0a8741b")
+ assert.NoError(t, err)
+
recoveredSigner, err := RecoverAddressFromEip712Signature(
"0x21f7d1f35979b125f6f7918fc16cb9101e5882d7",
"a9d5b4fd-ef30-4bb6-b9b6-4f2778f004fd",
@@ -21,7 +26,7 @@ func TestEIPSignature(t *testing.T) {
"console",
"0x21f7d1f35979b125f6f7918fc16cb9101e5882d7",
"1748608702",
- "0xe758880bc3d75e9433e0f50c9b40712b0bcf90f437a8c42ba6f8a5a3d144a5ce4b10b020c4eb728323daad49c4cc6329eaa45e3ea88c95d76e55b982f6a0a8741b",
+ signature,
)
assert.Equal(t, recoveredSigner, "0x21F7D1F35979B125f6F7918fC16Cb9101e5882d7")
diff --git a/clearnode/testing/client.go b/clearnode/testing/client.go
index b2f4d8f45..1649bef15 100644
--- a/clearnode/testing/client.go
+++ b/clearnode/testing/client.go
@@ -105,20 +105,7 @@ func NewSigner(privateKeyHex string) (*Signer, error) {
// Sign creates an ECDSA signature for the provided data
func (s *Signer) Sign(data []byte) ([]byte, error) {
- sig, err := nitrolite.Sign(data, s.privateKey)
- if err != nil {
- return nil, err
- }
-
- signature := make([]byte, 65)
- copy(signature[0:32], sig.R[:])
- copy(signature[32:64], sig.S[:])
-
- // EIP-155 style V: subtract 27 if needed
- if sig.V >= 27 {
- signature[64] = sig.V - 27
- }
- return signature, nil
+ return nitrolite.Sign(data, s.privateKey)
}
// GetAddress returns the address derived from the signer's public key
diff --git a/contract/script/setupChannel.s.sol b/contract/script/setupChannel.s.sol
index d933c65c7..f9865b46e 100644
--- a/contract/script/setupChannel.s.sol
+++ b/contract/script/setupChannel.s.sol
@@ -6,7 +6,8 @@ import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {Custody} from "../src/Custody.sol";
import {Utils} from "../src/Utils.sol";
-import {Channel, State, Allocation, Signature, ChannelStatus, StateIntent, Amount} from "../src/interfaces/Types.sol";
+import {Channel, State, Allocation, ChannelStatus, StateIntent, Amount} from "../src/interfaces/Types.sol";
+import {TestUtils} from "../test/TestUtils.sol";
contract SetupChannelScript is Script {
uint64 constant CHALLENGE_DURATION = 1 days;
@@ -40,8 +41,8 @@ contract SetupChannelScript is Script {
Channel memory channel = createChannel(USER_SESSION_KEY_ADDRESS, addresses[0], adjudicator);
State memory initialState = createInitialState(token, USER_SESSION_KEY_ADDRESS, addresses[0]);
- Signature memory userSig = signState(channel, initialState, USER_SESSION_KEY);
- initialState.sigs = new Signature[](1);
+ bytes memory userSig = signState(channel, initialState, USER_SESSION_KEY);
+ initialState.sigs = new bytes[](1);
initialState.sigs[0] = userSig;
vm.broadcast(addresses[1]);
@@ -78,17 +79,16 @@ contract SetupChannelScript is Script {
version: 0,
data: bytes(""),
allocations: allocations,
- sigs: new Signature[](0)
+ sigs: new bytes[](0)
});
}
function signState(Channel memory chan, State memory state, uint256 privateKey)
internal
view
- returns (Signature memory)
+ returns (bytes memory)
{
- bytes32 stateHash = Utils.getStateHash(chan, state);
- (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, stateHash);
- return Signature({v: v, r: r, s: s});
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(chan), state);
+ return TestUtils.sign(vm, privateKey, packedState);
}
}
diff --git a/contract/src/Custody.sol b/contract/src/Custody.sol
index c2a990550..3fb0e364b 100644
--- a/contract/src/Custody.sol
+++ b/contract/src/Custody.sol
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
+import {EIP712} from "lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {SafeERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol";
@@ -10,7 +11,7 @@ import {IChannelReader} from "./interfaces/IChannelReader.sol";
import {IComparable} from "./interfaces/IComparable.sol";
import {IChannel} from "./interfaces/IChannel.sol";
import {IDeposit} from "./interfaces/IDeposit.sol";
-import {Channel, State, Allocation, ChannelStatus, StateIntent, Signature, Amount} from "./interfaces/Types.sol";
+import {Channel, State, Allocation, ChannelStatus, StateIntent, Amount} from "./interfaces/Types.sol";
import {Utils} from "./Utils.sol";
/**
@@ -18,9 +19,10 @@ import {Utils} from "./Utils.sol";
* @notice A simple custody contract for state channels that delegates most state transition logic to an adjudicator
* @dev This implementation currently only supports 2 participant channels (CLIENT and SERVER)
*/
-contract Custody is IChannel, IDeposit, IChannelReader {
+contract Custody is IChannel, IDeposit, IChannelReader, EIP712 {
using EnumerableSet for EnumerableSet.Bytes32Set;
using SafeERC20 for IERC20;
+ using Utils for State;
// Errors
// TODO: sort errors
@@ -47,7 +49,11 @@ contract Custody is IChannel, IDeposit, IChannelReader {
uint256 constant CLIENT_IDX = 0; // Participant index for the channel creator
uint256 constant SERVER_IDX = 1; // Participant index for the server in clearnet context
- uint256 constant MIN_CHALLENGE_PERIOD = 1 hours;
+ uint256 public constant MIN_CHALLENGE_PERIOD = 1 hours;
+
+ bytes32 public constant CHALLENGE_STATE_TYPEHASH = keccak256(
+ "AllowChallengeStateHash(bytes32 channelId,uint8 intent,uint256 version,bytes data,Allocation[] allocations)Allocation(address destination,address token,uint256 amount)"
+ );
// Recommended structure to keep track of states
struct Metadata {
@@ -71,6 +77,12 @@ contract Custody is IChannel, IDeposit, IChannelReader {
mapping(bytes32 channelId => Metadata chMeta) internal _channels;
mapping(address account => Ledger ledger) internal _ledgers;
+ // ========== Constructor ==========
+ // TODO: make sure the last nitrolite sdk npm package version used
+ constructor() EIP712("Nitrolite:Custody", "0.2.24") {
+ // No state initialization needed
+ }
+
// ========== Read methods ==========
/**
@@ -226,10 +238,13 @@ contract Custody is IChannel, IDeposit, IChannelReader {
channelId = Utils.getChannelId(ch);
if (_channels[channelId].stage != ChannelStatus.VOID) revert InvalidStatus();
- bytes32 stateHash = Utils.getStateHash(ch, initial);
if (initial.sigs.length != 1) revert InvalidStateSignatures();
// TODO: later we can lift the restriction that first sig must be from CLIENT
- if (!Utils.verifySignature(stateHash, initial.sigs[CLIENT_IDX], ch.participants[CLIENT_IDX])) {
+ if (
+ !initial.verifyStateSignature(
+ channelId, _domainSeparatorV4(), initial.sigs[CLIENT_IDX], ch.participants[CLIENT_IDX]
+ )
+ ) {
revert InvalidStateSignatures();
}
@@ -294,7 +309,7 @@ contract Custody is IChannel, IDeposit, IChannelReader {
* @param sig Signature of SERVER on the funding state
* @return The channelId of the joined channel
*/
- function join(bytes32 channelId, uint256 index, Signature calldata sig) external returns (bytes32) {
+ function join(bytes32 channelId, uint256 index, bytes calldata sig) external returns (bytes32) {
Metadata storage meta = _channels[channelId];
// checks
@@ -304,11 +319,14 @@ contract Custody is IChannel, IDeposit, IChannelReader {
if (index != SERVER_IDX) revert InvalidParticipant();
if (meta.actualDeposits[SERVER_IDX].amount != 0) revert DepositAlreadyFulfilled();
- bytes32 stateHash = Utils.getStateHash(meta.chan, meta.lastValidState);
- if (!Utils.verifySignature(stateHash, sig, meta.chan.participants[SERVER_IDX])) revert InvalidStateSignatures();
+ if (
+ !meta.lastValidState.verifyStateSignature(
+ channelId, _domainSeparatorV4(), sig, meta.chan.participants[SERVER_IDX]
+ )
+ ) revert InvalidStateSignatures();
State memory lastValidState = meta.lastValidState;
- Signature[] memory sigs = new Signature[](PART_NUM);
+ bytes[] memory sigs = new bytes[](PART_NUM);
sigs[CLIENT_IDX] = lastValidState.sigs[CLIENT_IDX];
sigs[SERVER_IDX] = sig;
lastValidState.sigs = sigs;
@@ -390,7 +408,7 @@ contract Custody is IChannel, IDeposit, IChannelReader {
bytes32 channelId,
State calldata candidate,
State[] calldata proofs,
- Signature calldata challengerSig
+ bytes calldata challengerSig
) external {
Metadata storage meta = _channels[channelId];
@@ -399,11 +417,7 @@ contract Custody is IChannel, IDeposit, IChannelReader {
if (meta.stage == ChannelStatus.DISPUTE || meta.stage == ChannelStatus.FINAL) revert InvalidStatus();
if (candidate.intent == StateIntent.FINALIZE) revert InvalidState();
- _requireChallengerIsParticipant(
- Utils.getStateHash(meta.chan, candidate),
- [meta.chan.participants[CLIENT_IDX], meta.chan.participants[SERVER_IDX]],
- challengerSig
- );
+ _requireChallengerIsParticipant(channelId, candidate, meta.chan.participants, challengerSig);
StateIntent lastValidStateIntent = meta.lastValidState.intent;
@@ -592,6 +606,7 @@ contract Custody is IChannel, IDeposit, IChannelReader {
Metadata storage meta = _channels[channelId];
// effects
+ // NOTE: FINAL state is ephemeral because `meta` is deleted at the end of this function
meta.stage = ChannelStatus.FINAL;
// interactions
@@ -638,30 +653,52 @@ contract Custody is IChannel, IDeposit, IChannelReader {
* @param state The state to verify signatures for
* @return valid True if both signatures are valid
*/
- function _verifyAllSignatures(Channel memory chan, State memory state) internal view returns (bool valid) {
- bytes32 stateHash = Utils.getStateHash(chan, state);
-
+ function _verifyAllSignatures(Channel memory chan, State memory state) internal returns (bool valid) {
if (state.sigs.length != PART_NUM) {
return false;
}
+ bytes32 channelId = Utils.getChannelId(chan);
+
for (uint256 i = 0; i < PART_NUM; i++) {
- if (!Utils.verifySignature(stateHash, state.sigs[i], chan.participants[i])) return false;
+ if (!state.verifyStateSignature(channelId, _domainSeparatorV4(), state.sigs[i], chan.participants[i])) {
+ return false;
+ }
}
return true;
}
function _requireChallengerIsParticipant(
- bytes32 challengedStateHash,
- address[2] memory participants,
- Signature memory challengerSig
- ) internal pure {
- address challenger = Utils.recoverSigner(keccak256(abi.encode(challengedStateHash, "challenge")), challengerSig);
+ bytes32 channelId,
+ State memory state,
+ address[] memory participants,
+ bytes memory challengerSig
+ ) internal view {
+ // NOTE: ERC-6492 signature is NOT checked as at this point participants should already be deployed
+
+ // NOTE: the "challenge" suffix substitution for raw ECDSA and EIP-191 signatures
+ bytes memory packedChallengeState = abi.encodePacked(Utils.getPackedState(channelId, state), "challenge");
+ address rawSigner = Utils.recoverRawECDSASigner(packedChallengeState, challengerSig);
+ address eip191Signer = Utils.recoverEIP191Signer(packedChallengeState, challengerSig);
+ address eip712Signer = Utils.recoverStateEIP712Signer(
+ _domainSeparatorV4(), CHALLENGE_STATE_TYPEHASH, channelId, state, challengerSig
+ );
- if (challenger != participants[CLIENT_IDX] && challenger != participants[SERVER_IDX]) {
- revert InvalidChallengerSignature();
+ for (uint256 i = 0; i < participants.length; i++) {
+ address participant = participants[i];
+ if (participant.code.length != 0) {
+ if (Utils.isValidERC1271Signature(keccak256(packedChallengeState), challengerSig, participant)) {
+ return;
+ }
+ } else {
+ if (rawSigner == participant || eip191Signer == participant || eip712Signer == participant) {
+ return;
+ }
+ }
}
+
+ revert InvalidChallengerSignature();
}
/**
diff --git a/contract/src/Utils.sol b/contract/src/Utils.sol
index 2bdeef4b3..fff1597e7 100644
--- a/contract/src/Utils.sol
+++ b/contract/src/Utils.sol
@@ -2,7 +2,10 @@
pragma solidity ^0.8.13;
import {ECDSA} from "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
-import {Channel, State, Signature, StateIntent} from "./interfaces/Types.sol";
+import {MessageHashUtils} from "lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol";
+import {EIP712} from "lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol";
+import {IERC1271} from "lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol";
+import {STATE_TYPEHASH, Channel, State, StateIntent} from "./interfaces/Types.sol";
/**
* @title Channel Utilities
@@ -10,9 +13,20 @@ import {Channel, State, Signature, StateIntent} from "./interfaces/Types.sol";
*/
library Utils {
using ECDSA for bytes32;
+ using MessageHashUtils for bytes;
+ using { MessageHashUtils.toTypedDataHash } for bytes32;
- uint256 constant CLIENT = 0;
- uint256 constant SERVER = 1;
+ error ERC6492DeploymentFailed(address factory, bytes calldata_);
+ error ERC6492NoCode(address expectedSigner);
+
+ uint256 public constant CLIENT = 0;
+ uint256 public constant SERVER = 1;
+
+ bytes32 public constant NO_EIP712_SUPPORT = keccak256("NoEIP712Support");
+
+ bytes32 public constant ERC6492_DETECTION_SUFFIX =
+ 0x6492649264926492649264926492649264926492649264926492649264926492;
+ bytes4 public constant ERC1271_SUCCESS = 0x1626ba7e;
/**
* @notice Compute the unique identifier for a channel
@@ -28,48 +42,204 @@ library Utils {
}
/**
- * @notice Compute the hash of a channel state in a canonical way (ignoring the signature)
- * @param ch The channel struct
- * @param state The state struct
- * @return The state hash as bytes32
- * @dev The state hash is computed according to the specification in the README, using channelId, data, version, and allocations
+ * @notice Packs the channelId and the state into a byte array for signing
+ * @param channelId The unique identifier for the channel
+ * @param state The state struct to pack
+ * @return The packed channelId and state as bytes
*/
- function getStateHash(Channel memory ch, State memory state) internal view returns (bytes32) {
- bytes32 channelId = getChannelId(ch);
- return keccak256(abi.encode(channelId, state.intent, state.version, state.data, state.allocations));
+ function getPackedState(bytes32 channelId, State memory state) internal pure returns (bytes memory) {
+ return abi.encode(channelId, state.intent, state.version, state.data, state.allocations);
}
/**
* @notice Recovers the signer of a state hash from a signature
- * @param stateHash The hash of the state to verify (computed using the canonical form)
+ * @param message The message to verify the signature against
+ * @param sig The signature to verify
+ * @return The address of the signer
+ */
+ function recoverRawECDSASigner(bytes memory message, bytes memory sig) internal pure returns (address) {
+ // Verify the signature directly on the message hash without using EIP-191
+ return keccak256(message).recover(sig);
+ }
+
+ /**
+ * @notice Recovers the signer of a state hash using EIP-191 format
+ * @param message The message to verify the signature against
+ * @param sig The signature to verify
+ * @return The address of the signer
+ */
+ function recoverEIP191Signer(bytes memory message, bytes memory sig) internal pure returns (address) {
+ return message.toEthSignedMessageHash().recover(sig);
+ }
+
+ /**
+ * @notice Recovers the signer of a state hash using the EIP-712 format
+ * @param domainSeparator The EIP-712 domain separator
+ * @param structHash The hash of the struct to verify the signature against
+ * @param sig The signature to verify
+ * @return The address of the signer
+ */
+ function recoverEIP712Signer(bytes32 domainSeparator, bytes32 structHash, bytes memory sig)
+ internal
+ pure
+ returns (address)
+ {
+ return domainSeparator.toTypedDataHash(structHash).recover(sig);
+ }
+
+ /**
+ * @notice Recovers the signer of a state using EIP-712 format
+ * @param domainSeparator The EIP-712 domain separator
+ * @param typeHash The type hash for the state structure
+ * @param channelId The unique identifier for the channel
+ * @param state The state to verify
* @param sig The signature to verify
* @return The address of the signer
*/
- function recoverSigner(bytes32 stateHash, Signature memory sig) internal pure returns (address) {
- // Verify the signature directly on the stateHash without using EIP-191
- return ECDSA.recover(stateHash, sig.v, sig.r, sig.s);
+ function recoverStateEIP712Signer(
+ bytes32 domainSeparator,
+ bytes32 typeHash,
+ bytes32 channelId,
+ State memory state,
+ bytes memory sig
+ ) internal pure returns (address) {
+ return Utils.recoverEIP712Signer(
+ domainSeparator,
+ keccak256(
+ abi.encode(
+ typeHash,
+ channelId,
+ state.intent,
+ state.version,
+ keccak256(state.data),
+ keccak256(abi.encode(state.allocations))
+ )
+ ),
+ sig
+ );
}
/**
- * @notice Verifies that a state is signed by the specified participant
- * @param stateHash The hash of the state to verify (computed using the canonical form)
+ * @notice Verifies that a state is signed by the specified EOA participant in either raw ECDSA, EIP-191, or EIP-712 format
+ * @param state The state to verify
+ * @param channelId The ID of the channel
+ * @param domainSeparator The EIP-712 domain separator for the channel
* @param sig The signature to verify
* @param signer The address of the expected signer
* @return True if the signature is valid, false otherwise
*/
- function verifySignature(bytes32 stateHash, Signature memory sig, address signer) internal pure returns (bool) {
- address recoveredSigner = recoverSigner(stateHash, sig);
- return recoveredSigner == signer;
+ function verifyStateEOASignature(
+ State memory state,
+ bytes32 channelId,
+ bytes32 domainSeparator,
+ bytes memory sig,
+ address signer
+ ) internal pure returns (bool) {
+ bytes memory packedState = Utils.getPackedState(channelId, state);
+
+ address rawECDSASigner = Utils.recoverRawECDSASigner(packedState, sig);
+ if (rawECDSASigner == signer) {
+ return true;
+ }
+
+ address eip191Signer = Utils.recoverEIP191Signer(packedState, sig);
+ if (eip191Signer == signer) {
+ return true;
+ }
+
+ if (domainSeparator == NO_EIP712_SUPPORT) {
+ return false;
+ }
+
+ address eip712Signer = Utils.recoverStateEIP712Signer(domainSeparator, STATE_TYPEHASH, channelId, state, sig);
+ if (eip712Signer == signer) {
+ return true;
+ }
+
+ return false;
}
/**
- * @notice Compares two states for equality
- * @param a The first state to compare
- * @param b The second state to compare
- * @return True if the states are equal, false otherwise
+ * @notice Checks if a signature is valid by calling the expected signer contract according to the ERC-1271 standard
+ * @param msgHash The hash of the message to verify the signature against
+ * @param sig The signature to verify
+ * @param expectedSigner The address of the expected signer
+ * @return True if the signature is valid, false otherwise or if signer is not a contract
*/
- function statesAreEqual(State memory a, State memory b) internal pure returns (bool) {
- return keccak256(abi.encode(a)) == keccak256(abi.encode(b));
+ function isValidERC1271Signature(bytes32 msgHash, bytes memory sig, address expectedSigner)
+ internal
+ view
+ returns (bool)
+ {
+ return IERC1271(expectedSigner).isValidSignature(msgHash, sig) == ERC1271_SUCCESS;
+ }
+
+ /**
+ * @notice Checks the validity of a smart contract signature. If the expected signer has no code, it is deployed using the provided factory and calldata from the signature.
+ * Otherwise, it checks the signature using the ERC-1271 standard.
+ * @param msgHash The hash of the message to verify the signature against
+ * @param sig The signature to verify
+ * @param expectedSigner The address of the expected signer
+ * @return True if the signature is valid, false otherwise or if signer is not a contract
+ */
+ function isValidERC6492Signature(bytes32 msgHash, bytes memory sig, address expectedSigner)
+ internal
+ returns (bool)
+ {
+ (address create2Factory, bytes memory factoryCalldata, bytes memory originalSig) =
+ abi.decode(sig, (address, bytes, bytes));
+
+ if (expectedSigner.code.length == 0) {
+ (bool success,) = create2Factory.call(factoryCalldata);
+ require(success, ERC6492DeploymentFailed(create2Factory, factoryCalldata));
+ require(expectedSigner.code.length != 0, ERC6492NoCode(expectedSigner));
+ }
+
+ return IERC1271(expectedSigner).isValidSignature(msgHash, originalSig) == ERC1271_SUCCESS;
+ }
+
+ /**
+ * @notice Returns the last 32 bytes of a byte array
+ * @param data The byte array to extract from
+ * @return result The last 32 bytes of the byte array
+ */
+ function trailingBytes32(bytes memory data) internal pure returns (bytes32 result) {
+ if (data.length < 32) {
+ return bytes32(0);
+ }
+ assembly {
+ result := mload(add(data, mload(data)))
+ }
+ }
+
+ /**
+ * @notice Verifies that a state is signed by the specified participant as an EOA or a Smart Contract
+ * @param state The state to verify
+ * @param channelId The ID of the channel
+ * @param domainSeparator The EIP-712 domain separator for the channel
+ * @param sig The signature to verify
+ * @param signer The address of the expected signer
+ * @return True if the signature is valid, false otherwise
+ */
+ function verifyStateSignature(
+ State memory state,
+ bytes32 channelId,
+ bytes32 domainSeparator,
+ bytes memory sig,
+ address signer
+ ) internal returns (bool) {
+ // NOTE: both EIP-1271 and EIP-6492 signatures use message hash
+ bytes32 stateHash = keccak256(Utils.getPackedState(channelId, state));
+
+ if (trailingBytes32(sig) == ERC6492_DETECTION_SUFFIX) {
+ return isValidERC6492Signature(stateHash, sig, signer);
+ }
+
+ if (signer.code.length != 0) {
+ return isValidERC1271Signature(stateHash, sig, signer);
+ }
+
+ return Utils.verifyStateEOASignature(state, channelId, domainSeparator, sig, signer);
}
/**
@@ -77,9 +247,13 @@ library Utils {
* @dev Initial states must have version 0 and INITIALIZE intent
* @param state The state to validate
* @param chan The channel configuration
+ * @param domainSeparator The EIP-712 domain separator for the channel
* @return True if the state is a valid initial state, false otherwise
*/
- function validateInitialState(State memory state, Channel memory chan) internal view returns (bool) {
+ function validateInitialState(State memory state, Channel memory chan, bytes32 domainSeparator)
+ internal
+ returns (bool)
+ {
if (state.version != 0) {
return false;
}
@@ -88,7 +262,7 @@ library Utils {
return false;
}
- return validateUnanimousSignatures(state, chan);
+ return validateUnanimousStateSignatures(state, chan, domainSeparator);
}
/**
@@ -96,18 +270,31 @@ library Utils {
* @dev For 2-participant channels, both must sign to establish unanimous consent
* @param state The state to validate
* @param chan The channel configuration
+ * @param domainSeparator The EIP-712 domain separator for the channel
* @return True if the state has valid signatures from both participants, false otherwise
*/
- function validateUnanimousSignatures(State memory state, Channel memory chan) internal view returns (bool) {
+ function validateUnanimousStateSignatures(State memory state, Channel memory chan, bytes32 domainSeparator)
+ internal
+ returns (bool)
+ {
if (state.sigs.length != 2) {
return false;
}
- // Compute the state hash for signature verification.
- bytes32 stateHash = getStateHash(chan, state);
+ bytes32 channelId = getChannelId(chan);
- return verifySignature(stateHash, state.sigs[0], chan.participants[CLIENT])
- && verifySignature(stateHash, state.sigs[1], chan.participants[SERVER]);
+ return Utils.verifyStateSignature(state, channelId, domainSeparator, state.sigs[0], chan.participants[CLIENT])
+ && Utils.verifyStateSignature(state, channelId, domainSeparator, state.sigs[1], chan.participants[SERVER]);
+ }
+
+ /**
+ * @notice Compares two states for equality
+ * @param a The first state to compare
+ * @param b The second state to compare
+ * @return True if the states are equal, false otherwise
+ */
+ function statesAreEqual(State memory a, State memory b) internal pure returns (bool) {
+ return keccak256(abi.encode(a)) == keccak256(abi.encode(b));
}
/**
diff --git a/contract/src/adjudicators/ConsensusTransition.sol b/contract/src/adjudicators/ConsensusTransition.sol
index d93d44505..94553f89e 100644
--- a/contract/src/adjudicators/ConsensusTransition.sol
+++ b/contract/src/adjudicators/ConsensusTransition.sol
@@ -2,7 +2,8 @@
pragma solidity ^0.8.13;
import {IAdjudicator} from "../interfaces/IAdjudicator.sol";
-import {Channel, State, Allocation, Signature, StateIntent} from "../interfaces/Types.sol";
+import {Channel, State, Allocation, StateIntent} from "../interfaces/Types.sol";
+import {EIP712AdjudicatorBase} from "./EIP712AdjudicatorBase.sol";
import {Utils} from "../Utils.sol";
/**
@@ -10,19 +11,25 @@ import {Utils} from "../Utils.sol";
* @notice An adjudicator that validates state based on mutual signatures from both participants and the transition from the previous state.
* @dev Any state is considered valid as long as it's signed by both participants and is a valid transition from the previous state.
*/
-contract ConsensusTransition is IAdjudicator {
+contract ConsensusTransition is IAdjudicator, EIP712AdjudicatorBase {
using Utils for State;
/**
- * @notice Validates that the state is signed by both participants
- * @param chan The channel configuration
- * @param candidate The proposed state
- * @param proofs Array of previous states
- * @return valid True if the state is valid, false otherwise
+ * @notice Constructor for the ConsensusTransition adjudicator.
+ * @param owner The owner of the adjudicator contract.
+ * @param channelImpl The address of the channel implementation contract.
+ */
+ constructor(address owner, address channelImpl) EIP712AdjudicatorBase(owner, channelImpl) {}
+
+ /**
+ * @notice Validates that the state is signed by both participants.
+ * @param chan The channel configuration.
+ * @param candidate The proposed state.
+ * @param proofs Array of previous states.
+ * @return valid True if the state is valid, false otherwise.
*/
function adjudicate(Channel calldata chan, State calldata candidate, State[] calldata proofs)
external
- view
override
returns (bool valid)
{
@@ -35,14 +42,18 @@ contract ConsensusTransition is IAdjudicator {
return false;
}
+ bytes32 channelImplDomainSeparator = getChannelImplDomainSeparator();
+
// proof is Initialize State
if (candidate.version == 1) {
- return proofs[0].validateTransitionTo(candidate) && proofs[0].validateInitialState(chan)
- && candidate.validateUnanimousSignatures(chan);
+ return proofs[0].validateTransitionTo(candidate)
+ && proofs[0].validateInitialState(chan, channelImplDomainSeparator)
+ && candidate.validateUnanimousStateSignatures(chan, channelImplDomainSeparator);
}
// proof is Operate or Resize State (both have same validation)
- return proofs[0].validateTransitionTo(candidate) && proofs[0].validateUnanimousSignatures(chan)
- && candidate.validateUnanimousSignatures(chan);
+ return proofs[0].validateTransitionTo(candidate)
+ && proofs[0].validateUnanimousStateSignatures(chan, channelImplDomainSeparator)
+ && candidate.validateUnanimousStateSignatures(chan, channelImplDomainSeparator);
}
}
diff --git a/contract/src/adjudicators/Counter.sol b/contract/src/adjudicators/Counter.sol
index 6a057e59c..111da9200 100644
--- a/contract/src/adjudicators/Counter.sol
+++ b/contract/src/adjudicators/Counter.sol
@@ -2,7 +2,7 @@
pragma solidity ^0.8.13;
import {IAdjudicator} from "../interfaces/IAdjudicator.sol";
-import {Channel, State, Allocation, Signature, StateIntent} from "../interfaces/Types.sol";
+import {Channel, State, Allocation, StateIntent} from "../interfaces/Types.sol";
import {Utils} from "../Utils.sol";
/**
@@ -32,7 +32,6 @@ contract Counter is IAdjudicator {
*/
function adjudicate(Channel calldata chan, State calldata candidate, State[] calldata proofs)
external
- view
override
returns (bool valid)
{
@@ -59,7 +58,7 @@ contract Counter is IAdjudicator {
// proof is Initialize State
if (candidate.version == 1) {
return proofs[0].validateTransitionTo(candidate) && _validateAppTransitionTo(proofs[0].data, candidate.data)
- && proofs[0].validateInitialState(chan) && _validateStateSig(chan, candidate);
+ && proofs[0].validateInitialState(chan, Utils.NO_EIP712_SUPPORT) && _validateStateSig(chan, candidate);
}
bytes memory proofData = proofs[0].data;
@@ -85,7 +84,7 @@ contract Counter is IAdjudicator {
return candidateDataDecoded.target == previousDataDecoded.target;
}
- function _validateStateSig(Channel calldata chan, State calldata state) internal view returns (bool) {
+ function _validateStateSig(Channel calldata chan, State calldata state) internal returns (bool) {
if (state.sigs.length != 1) {
return false;
}
@@ -97,6 +96,8 @@ contract Counter is IAdjudicator {
signerIdx = 1; // guest signer
}
- return Utils.verifySignature(Utils.getStateHash(chan, state), state.sigs[0], chan.participants[signerIdx]);
+ return state.verifyStateSignature(
+ Utils.getChannelId(chan), Utils.NO_EIP712_SUPPORT, state.sigs[0], chan.participants[signerIdx]
+ );
}
}
diff --git a/contract/src/adjudicators/EIP712AdjudicatorBase.sol b/contract/src/adjudicators/EIP712AdjudicatorBase.sol
new file mode 100644
index 000000000..29b611f22
--- /dev/null
+++ b/contract/src/adjudicators/EIP712AdjudicatorBase.sol
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {IERC5267} from "lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol";
+import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
+import {Ownable2Step} from "lib/openzeppelin-contracts/contracts/access/Ownable2Step.sol";
+
+import {Utils} from "../Utils.sol";
+
+/**
+ * @title EIP712 Adjudicator Base
+ * @notice Base contract for EIP712 compliant adjudicators when EIP-712 domain specifies channel implementation contract address as a verifying contract.
+ * @dev Contains a link to the channel implementation contract and provides a method to retrieve its domain separator.
+ * Channel implementation contract must be ERC-5267 compliant.
+ */
+abstract contract EIP712AdjudicatorBase is Ownable2Step {
+ /// Taken from EIP-712 specification
+ /// https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
+ bytes32 private constant TYPE_HASH =
+ keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
+
+ IERC5267 public channelImpl;
+
+ /**
+ * @notice Constructor for the EIP712AdjudicatorBase.
+ * @param owner The owner of the adjudicator contract.
+ * @param channelImpl_ The address of the channel implementation contract.
+ */
+ constructor(address owner, address channelImpl_) Ownable(owner) {
+ channelImpl = IERC5267(channelImpl_);
+ }
+
+ /**
+ * @notice Returns the domain separator for the channel implementation contract.
+ * @return The domain separator as bytes32.
+ * @dev This function retrieves the domain separator from the channel implementation contract.
+ */
+ function getChannelImplDomainSeparator() public view returns (bytes32) {
+ if (address(channelImpl).code.length == 0) {
+ // NOTE: soft failure, if channel implementation contract is not set or does not exist
+ return Utils.NO_EIP712_SUPPORT;
+ }
+
+ try channelImpl.eip712Domain() returns (
+ bytes1,
+ string memory name,
+ string memory version,
+ uint256 chainId,
+ address verifyingContract,
+ bytes32,
+ uint256[] memory
+ ) {
+ /// Taken from EIP-712 specification
+ /// https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
+ return keccak256(
+ abi.encode(TYPE_HASH, keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract)
+ );
+ } catch {
+ // NOTE: soft failure, if channel implementation contract does not support EIP-712
+ return Utils.NO_EIP712_SUPPORT;
+ }
+ }
+
+ /**
+ * @notice Sets the channel implementation contract address.
+ * @dev Callable only by Owner.
+ * @param channelImpl_ The address of the channel implementation contract.
+ */
+ function setChannelImpl(address channelImpl_) external onlyOwner {
+ channelImpl = IERC5267(channelImpl_);
+ }
+}
diff --git a/contract/src/adjudicators/Remittance.sol b/contract/src/adjudicators/Remittance.sol
index 1cc8bd909..245ecc993 100644
--- a/contract/src/adjudicators/Remittance.sol
+++ b/contract/src/adjudicators/Remittance.sol
@@ -3,7 +3,8 @@ pragma solidity ^0.8.13;
import {IAdjudicator} from "../interfaces/IAdjudicator.sol";
import {IComparable} from "../interfaces/IComparable.sol";
-import {Channel, State, Allocation, Signature, Amount, StateIntent} from "../interfaces/Types.sol";
+import {Channel, State, Allocation, Amount, StateIntent} from "../interfaces/Types.sol";
+import {EIP712AdjudicatorBase} from "./EIP712AdjudicatorBase.sol";
import {Utils} from "../Utils.sol";
/**
@@ -13,16 +14,16 @@ import {Utils} from "../Utils.sol";
* This prevents forging "more balance out of thin air" since only the person giving up funds
* needs to sign, making it secure for two-party channels with single allocations per party
*/
-contract RemittanceAdjudicator is IAdjudicator, IComparable {
+contract RemittanceAdjudicator is IAdjudicator, IComparable, EIP712AdjudicatorBase {
using Utils for State;
uint8 constant CREATOR = 0;
uint8 constant BROKER = 1;
/**
- * @dev Remittance represents a payment transfer from one participant to another
- * @param sender Index of the participant sending funds (0 for CREATOR, 1 for BROKER)
- * @param amount Amount and token being transferred
+ * @dev Remittance represents a payment transfer from one participant to another.
+ * @param sender Index of the participant sending funds (0 for CREATOR, 1 for BROKER).
+ * @param amount Amount and token being transferred.
*/
struct Remittance {
uint8 sender; // Index of the participant sending funds
@@ -30,16 +31,22 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
}
/**
- * @notice Validates state transitions based on the principle that only the sender needs to sign
- * @param chan The channel configuration
- * @param candidate The proposed state
- * @param proofs Array containing previous states in increasing order up to a starting state, which is either INITIALIZE, RESIZE or state signed by the other party
+ * @notice Constructor for the Remittance adjudicator.
+ * @param owner The owner of the adjudicator contract.
+ * @param channelImpl The address of the channel implementation contract.
+ */
+ constructor(address owner, address channelImpl) EIP712AdjudicatorBase(owner, channelImpl) {}
+
+ /**
+ * @notice Validates state transitions based on the principle that only the sender needs to sign.
+ * @param chan The channel configuration.
+ * @param candidate The proposed state.
+ * @param proofs Array containing previous states in increasing order up to a starting state, which is either INITIALIZE, RESIZE or state signed by the other party.
* I.e. if the same party has consequently been signing Remittances, they should supply all their subsequent states, based on a starting state.
- * @return valid True if the state transition is valid, false otherwise
+ * @return valid True if the state transition is valid, false otherwise.
*/
function adjudicate(Channel calldata chan, State calldata candidate, State[] calldata proofs)
external
- view
override
returns (bool valid)
{
@@ -48,6 +55,8 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
return false;
}
+ bytes32 channelImplDomainSeparator = getChannelImplDomainSeparator();
+
Remittance memory candidateRemittance = abi.decode(candidate.data, (Remittance));
// The last proof must be either INITIALIZE, RESIZE, or signed by the other party
@@ -55,11 +64,11 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
// Check if the last proof is a valid starting point
if (earliestProof.intent == StateIntent.INITIALIZE) {
- if (!earliestProof.validateInitialState(chan)) {
+ if (!earliestProof.validateInitialState(chan, channelImplDomainSeparator)) {
return false;
}
} else if (earliestProof.intent == StateIntent.RESIZE) {
- if (!earliestProof.validateUnanimousSignatures(chan)) {
+ if (!earliestProof.validateUnanimousStateSignatures(chan, channelImplDomainSeparator)) {
return false;
}
// NOTE: "extract" resize amounts, keep only the RESIZE state data
@@ -76,8 +85,14 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
uint8 otherParty = candidateRemittance.sender == CREATOR ? BROKER : CREATOR;
// Verify the other party signed the last proof
- bytes32 lastProofHash = Utils.getStateHash(chan, earliestProof);
- if (!Utils.verifySignature(lastProofHash, earliestProof.sigs[0], chan.participants[otherParty])) {
+ if (
+ !earliestProof.verifyStateSignature(
+ Utils.getChannelId(chan),
+ channelImplDomainSeparator,
+ earliestProof.sigs[0],
+ chan.participants[otherParty]
+ )
+ ) {
return false;
}
} else {
@@ -89,7 +104,7 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
uint256 proofsLength = proofs.length;
if (proofsLength == 0) {
- if (!_validateRemittanceState(chan, candidate)) {
+ if (!_validateRemittanceState(channelImplDomainSeparator, chan, candidate)) {
return false;
}
@@ -102,7 +117,7 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
previousState = currentState;
currentState = proofs[currIdx];
- if (!_validateRemittanceState(chan, currentState)) {
+ if (!_validateRemittanceState(channelImplDomainSeparator, chan, currentState)) {
return false;
}
@@ -114,7 +129,10 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
return true;
}
- function _validateRemittanceState(Channel calldata chan, State memory state) internal view returns (bool) {
+ function _validateRemittanceState(bytes32 domainSeparator, Channel calldata chan, State memory state)
+ internal
+ returns (bool)
+ {
if (state.intent != StateIntent.OPERATE) {
return false;
}
@@ -134,8 +152,9 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
}
// Verify signature is from the sender
- bytes32 stateHash = Utils.getStateHash(chan, state);
- return Utils.verifySignature(stateHash, state.sigs[0], chan.participants[currentRemittance.sender]);
+ return state.verifyStateSignature(
+ Utils.getChannelId(chan), domainSeparator, state.sigs[0], chan.participants[currentRemittance.sender]
+ );
}
function _validateRemittanceTransition(State memory previousState, State memory currentState)
@@ -144,7 +163,7 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
returns (bool)
{
// basic validations: version, allocations sum
- if (!previousState.validateTransitionTo(currentState)) {
+ if (!Utils.validateTransitionTo(previousState, currentState)) {
return false;
}
@@ -188,9 +207,9 @@ contract RemittanceAdjudicator is IAdjudicator, IComparable {
}
/**
- * @notice Compares two states to determine their relative ordering
- * @param candidate The state being evaluated
- * @param previous The reference state to compare against
+ * @notice Compares two states to determine their relative ordering.
+ * @param candidate The state being evaluated.
+ * @param previous The reference state to compare against.
* @return result The comparison result:
* -1: candidate < previous (candidate is older)
* 0: candidate == previous (same recency)
diff --git a/contract/src/adjudicators/SimpleConsensus.sol b/contract/src/adjudicators/SimpleConsensus.sol
index 6ffb2d80d..99b1de3cb 100644
--- a/contract/src/adjudicators/SimpleConsensus.sol
+++ b/contract/src/adjudicators/SimpleConsensus.sol
@@ -2,7 +2,8 @@
pragma solidity ^0.8.13;
import {IAdjudicator} from "../interfaces/IAdjudicator.sol";
-import {Channel, State, Allocation, Signature, StateIntent} from "../interfaces/Types.sol";
+import {Channel, State, Allocation, StateIntent} from "../interfaces/Types.sol";
+import {EIP712AdjudicatorBase} from "./EIP712AdjudicatorBase.sol";
import {Utils} from "../Utils.sol";
/**
@@ -10,19 +11,25 @@ import {Utils} from "../Utils.sol";
* @notice An adjudicator that validates state based on mutual signatures from both participants.
* @dev Any state is considered valid as long as it's signed by both participants.
*/
-contract SimpleConsensus is IAdjudicator {
+contract SimpleConsensus is IAdjudicator, EIP712AdjudicatorBase {
using Utils for State;
/**
- * @notice Validates that the state is signed by both participants
- * @param chan The channel configuration
- * @param candidate The proposed state
- * @param proofs Array of previous states (unused in this implementation)
- * @return valid True if the state is valid, false otherwise
+ * @notice Constructor for the SimpleConsensus adjudicator.
+ * @param owner The owner of the adjudicator contract.
+ * @param channelImpl The address of the channel implementation contract.
+ */
+ constructor(address owner, address channelImpl) EIP712AdjudicatorBase(owner, channelImpl) {}
+
+ /**
+ * @notice Validates that the state is signed by both participants.
+ * @param chan The channel configuration.
+ * @param candidate The proposed state.
+ * @param proofs Array of previous states (unused in this implementation).
+ * @return valid True if the state is valid, false otherwise.
*/
function adjudicate(Channel calldata chan, State calldata candidate, State[] calldata proofs)
external
- view
override
returns (bool valid)
{
@@ -30,11 +37,14 @@ contract SimpleConsensus is IAdjudicator {
return false;
}
+ bytes32 channelImplDomainSeparator = getChannelImplDomainSeparator();
+
if (candidate.version == 0) {
- return candidate.validateInitialState(chan);
+ return candidate.validateInitialState(chan, channelImplDomainSeparator);
}
// proof is Operate or Resize State (both have same validation)
- return candidate.intent != StateIntent.INITIALIZE && candidate.validateUnanimousSignatures(chan);
+ return candidate.intent != StateIntent.INITIALIZE
+ && candidate.validateUnanimousStateSignatures(chan, channelImplDomainSeparator);
}
}
diff --git a/contract/src/interfaces/IAdjudicator.sol b/contract/src/interfaces/IAdjudicator.sol
index 300a61385..7bf5838a6 100644
--- a/contract/src/interfaces/IAdjudicator.sol
+++ b/contract/src/interfaces/IAdjudicator.sol
@@ -19,6 +19,5 @@ interface IAdjudicator {
*/
function adjudicate(Channel calldata chan, State calldata candidate, State[] calldata proofs)
external
- view
returns (bool valid);
}
diff --git a/contract/src/interfaces/IChannel.sol b/contract/src/interfaces/IChannel.sol
index 95d8c85d7..e0d5c2675 100644
--- a/contract/src/interfaces/IChannel.sol
+++ b/contract/src/interfaces/IChannel.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
-import {Channel, State, Signature, Amount} from "./Types.sol";
+import {Channel, State, Amount} from "./Types.sol";
/**
* @title State Channel Interface
@@ -76,7 +76,7 @@ interface IChannel {
* @param sig Signature of the participant on the funding state
* @return channelId Unique identifier for the joined channel
*/
- function join(bytes32 channelId, uint256 index, Signature calldata sig) external returns (bytes32);
+ function join(bytes32 channelId, uint256 index, bytes calldata sig) external returns (bytes32);
/**
* @notice Finalizes a channel with a mutually signed closing state
@@ -110,7 +110,7 @@ interface IChannel {
bytes32 channelId,
State calldata candidate,
State[] calldata proofs,
- Signature calldata challengerSig
+ bytes calldata challengerSig
) external;
/**
diff --git a/contract/src/interfaces/Types.sol b/contract/src/interfaces/Types.sol
index f0a96c923..22fca8853 100644
--- a/contract/src/interfaces/Types.sol
+++ b/contract/src/interfaces/Types.sol
@@ -6,15 +6,10 @@ pragma solidity ^0.8.13;
* @notice Shared types used in the Nitrolite state channel system
*/
-/**
- * @notice Signature structure for digital signatures
- * @dev Used for off-chain signatures verification in the state channel protocol
- */
-struct Signature {
- uint8 v; // Recovery ID
- bytes32 r; // R component of the signature
- bytes32 s; // S component of the signature
-}
+/// @dev EIP-712 domain separator type hash for state channel protocol
+bytes32 constant STATE_TYPEHASH = keccak256(
+ "AllowStateHash(bytes32 channelId,uint8 intent,uint256 version,bytes data,Allocation[] allocations)Allocation(address destination,address token,uint256 amount)"
+);
/**
* @notice Amount structure for token value storage
@@ -80,7 +75,7 @@ struct State {
uint256 version; // State version incremental number to compare most recent
bytes data; // Application data encoded, decoded by the adjudicator for business logic
Allocation[] allocations; // Combined asset allocation and destination for each participant
- Signature[] sigs; // stateHash signatures from participants
+ bytes[] sigs; // stateHash signatures from participants
}
/**
diff --git a/contract/test/Custody.t.sol b/contract/test/Custody.t.sol
index 16d1738a2..4976b5016 100644
--- a/contract/test/Custody.t.sol
+++ b/contract/test/Custody.t.sol
@@ -12,25 +12,30 @@ pragma solidity ^0.8.13;
* - Version 98: Counter-challenge state
* - Version 100: Closing state
*/
-import {Test, console} from "lib/forge-std/src/Test.sol";
+import {Test} from "lib/forge-std/src/Test.sol";
import {IERC20} from "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {MessageHashUtils} from "lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol";
import {ECDSA} from "lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol";
+import {MockFlagERC1271} from "./mocks/MockFlagERC1271.sol"; // import to allow easier artifact fetching for `getCode` cheat code
import {TestUtils} from "./TestUtils.sol";
import {Custody} from "../src/Custody.sol";
-import {Channel, State, Allocation, Signature, ChannelStatus, StateIntent, Amount} from "../src/interfaces/Types.sol";
+import {
+ Channel, State, Allocation, ChannelStatus, StateIntent, Amount, STATE_TYPEHASH
+} from "../src/interfaces/Types.sol";
import {Utils} from "../src/Utils.sol";
import {FlagAdjudicator} from "./mocks/FlagAdjudicator.sol";
import {MockERC20} from "./mocks/MockERC20.sol";
-contract CustodyTest is Test {
+contract CustodyTest_Base is Test {
Custody public custody;
FlagAdjudicator public adjudicator;
MockERC20 public token;
+ bytes32 custodyDomainSeparator;
+
// Private keys for testing
uint256 constant hostSKPrivKey = 1;
uint256 constant guestSKPrivKey = 2;
@@ -53,7 +58,14 @@ contract CustodyTest is Test {
uint256 constant DEPOSIT_AMOUNT = 1000;
uint256 constant INITIAL_BALANCE = 10000;
- function setUp() public {
+ function setUp() public virtual {
+ // Deploy contracts
+ custody = new Custody();
+ adjudicator = new FlagAdjudicator();
+ token = new MockERC20("Test Token", "TST", 18);
+
+ custodyDomainSeparator = TestUtils.buildDomainSeparatorForContract(custody);
+
// Set up user addresses from private keys
hostSK = vm.addr(hostSKPrivKey);
guestSK = vm.addr(guestSKPrivKey);
@@ -62,11 +74,6 @@ contract CustodyTest is Test {
hostWallet = vm.addr(hostWalletPrivKey);
guestWallet = vm.addr(guestWalletPrivKey);
- // Deploy contracts
- custody = new Custody();
- adjudicator = new FlagAdjudicator();
- token = new MockERC20("Test Token", "TST", 18);
-
// Fund accounts
token.mint(hostSK, INITIAL_BALANCE);
token.mint(guestSK, INITIAL_BALANCE);
@@ -157,7 +164,7 @@ contract CustodyTest is Test {
version: 0, // Initial state has version 0
data: bytes(""), // Empty data
allocations: allocations,
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
}
@@ -172,7 +179,7 @@ contract CustodyTest is Test {
version: 0, // Initial state has version 0
data: bytes(""), // Empty data
allocations: allocations,
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
}
@@ -188,7 +195,7 @@ contract CustodyTest is Test {
version: 0, // Initial state has version 0
data: bytes(""), // Empty data
allocations: allocations,
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
}
@@ -203,29 +210,36 @@ contract CustodyTest is Test {
version: 0, // Initial state has version 0
data: bytes(""), // Empty data
allocations: allocations,
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
}
function signState(Channel memory chan, State memory state, uint256 privateKey)
internal
view
- returns (Signature memory)
+ returns (bytes memory)
{
- bytes32 stateHash = Utils.getStateHash(chan, state);
- (uint8 v, bytes32 r, bytes32 s) = TestUtils.sign(vm, privateKey, stateHash);
- return Signature({v: v, r: r, s: s});
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(chan), state);
+ return TestUtils.sign(vm, privateKey, packedState);
}
function signChallenge(Channel memory chan, State memory state, uint256 privateKey)
internal
view
- returns (Signature memory)
+ returns (bytes memory)
{
- bytes32 stateHash = Utils.getStateHash(chan, state);
- bytes32 challengeHash = keccak256(abi.encode(stateHash, "challenge"));
- (uint8 v, bytes32 r, bytes32 s) = TestUtils.sign(vm, privateKey, challengeHash);
- return Signature({v: v, r: r, s: s});
+ bytes memory packedChallengeState = abi.encodePacked(Utils.getPackedState(Utils.getChannelId(chan), state), "challenge");
+ return TestUtils.sign(vm, privateKey, packedChallengeState);
+ }
+
+ function signChallengeEIP712(Channel memory chan, State memory state, uint256 privateKey)
+ internal
+ view
+ returns (bytes memory)
+ {
+ return TestUtils.signStateEIP712(
+ vm, Utils.getChannelId(chan), state, custody.CHALLENGE_STATE_TYPEHASH(), custodyDomainSeparator, privateKey
+ );
}
function depositTokens(address user, uint256 amount) internal {
@@ -236,7 +250,265 @@ contract CustodyTest is Test {
function skipChallengeTime() internal {
skip(CHALLENGE_DURATION + 1);
}
+}
+
+contract CustodyTest_challenge is CustodyTest_Base {
+ // Pre-challenge setup variables
+ Channel internal chan;
+ State internal initialState;
+ bytes32 internal channelId;
+
+ function setUp() public override {
+ super.setUp();
+
+ // Create and fund a channel with both participants
+ chan = createTestChannelWithSK();
+ initialState = createInitialStateWithSK();
+
+ // Set up signatures
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
+ hostSigs[0] = hostSig;
+ initialState.sigs = hostSigs;
+
+ // Create channel with host
+ depositTokens(hostSK, DEPOSIT_AMOUNT * 2);
+ vm.prank(hostSK);
+ channelId = custody.create(chan, initialState);
+
+ // Guest joins the channel
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
+ vm.prank(guestSK);
+ custody.join(channelId, 1, guestSig);
+ }
+
+ function test_success_rawECDSASig() public {
+ // 1. Create a challenge state
+ State memory challengeState = initialState;
+ challengeState.intent = StateIntent.OPERATE;
+ challengeState.data = abi.encode(42);
+ challengeState.version = 97; // Version 97 indicates a challenge state
+
+ // Host signs the challenge state
+ bytes memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
+ bytes[] memory challengeSigs = new bytes[](1);
+ challengeSigs[0] = hostChallengeSig;
+ challengeState.sigs = challengeSigs;
+
+ // 2. Host challenges with this state and signs the challenge
+ bytes memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
+ vm.prank(hostSK);
+ custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
+
+ // 3. Verify channel is in CHALLENGED status
+ (, ChannelStatus status,, uint256 challengeExpiry,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.DISPUTE, "Channel should be in DISPUTE status");
+ assertTrue(challengeExpiry > block.timestamp, "Channel should have challengeExpiry set in future");
+ }
+
+ function test_success_EIP712Sig() public {
+ // 1. Create a challenge state
+ State memory challengeState = initialState;
+ challengeState.intent = StateIntent.OPERATE;
+ challengeState.data = abi.encode(42);
+ challengeState.version = 97; // Version 97 indicates a challenge state
+
+ // Host signs the challenge state
+ bytes memory hostChallengeSig = TestUtils.signStateEIP712(
+ vm, Utils.getChannelId(chan), challengeState, STATE_TYPEHASH, custodyDomainSeparator, hostSKPrivKey
+ );
+ bytes[] memory challengeSigs = new bytes[](1);
+ challengeSigs[0] = hostChallengeSig;
+ challengeState.sigs = challengeSigs;
+
+ // 2. Host challenges with this state and signs the challenge
+ bytes memory hostChallengerSig = signChallengeEIP712(chan, challengeState, hostSKPrivKey);
+ vm.prank(hostSK);
+ custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
+
+ // 3. Verify channel is in CHALLENGED status
+ (, ChannelStatus status,, uint256 challengeExpiry,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.DISPUTE, "Channel should be in DISPUTE status");
+ assertTrue(challengeExpiry > block.timestamp, "Channel should have challengeExpiry set in future");
+ }
+
+ function test_success_EIP1271Sig() public {
+ // 0. Deploy an EIP-1271 contract at hostSK address
+ bool flag = true; // Assume the EIP-1271 contract always returns true for valid signatures
+ deployCodeTo("MockFlagERC1271", abi.encode(flag), hostSK);
+ // 1. Create a challenge state
+ State memory challengeState = initialState;
+ challengeState.intent = StateIntent.OPERATE;
+ challengeState.data = abi.encode(42);
+ challengeState.version = 97; // Version 97 indicates a challenge state
+
+ // Host signs the challenge state. NOTE: assume signature verifier accepts raw ECDSA signatures
+ bytes memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
+ bytes[] memory challengeSigs = new bytes[](1);
+ challengeSigs[0] = hostChallengeSig;
+ challengeState.sigs = challengeSigs;
+
+ // 2. Host challenges with this state and signs the challenge
+ bytes memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
+ vm.prank(hostSK);
+ custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
+
+ // 3. Verify channel is in CHALLENGED status
+ (, ChannelStatus status,, uint256 challengeExpiry,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.DISPUTE, "Channel should be in DISPUTE status");
+ assertTrue(challengeExpiry > block.timestamp, "Channel should have challengeExpiry set in future");
+ }
+
+ function test_revert_whenOngoingChallenge() public {
+ // 1. Create and submit first challenge state
+ State memory challengeState = initialState;
+ challengeState.intent = StateIntent.OPERATE;
+ challengeState.data = abi.encode(42);
+ challengeState.version = 97; // Version 97 indicates a challenge state
+
+ // Host signs the challenge state
+ bytes memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
+ bytes[] memory challengeSigs = new bytes[](1);
+ challengeSigs[0] = hostChallengeSig;
+ challengeState.sigs = challengeSigs;
+ bytes memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
+
+ // Submit first challenge
+ vm.prank(hostSK);
+ custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
+
+ // 2. Create a new challenge state with the same version number
+ State memory sameVersionChallenge = initialState;
+ sameVersionChallenge.intent = StateIntent.OPERATE;
+ sameVersionChallenge.data = abi.encode(43); // Different data but same version
+ sameVersionChallenge.version = 97; // Same version as previous challenge (97)
+
+ // Host signs the same version challenge
+ bytes memory hostSameVersionSig = signState(chan, sameVersionChallenge, hostSKPrivKey);
+ bytes[] memory sameVersionSigs = new bytes[](1);
+ sameVersionSigs[0] = hostSameVersionSig;
+ sameVersionChallenge.sigs = sameVersionSigs;
+ bytes memory sameVersionChallengerSig = signChallenge(chan, sameVersionChallenge, hostSKPrivKey);
+
+ // 3. Try to challenge with the same version - should revert
+ vm.prank(hostSK);
+ vm.expectRevert(Custody.InvalidStatus.selector);
+ custody.challenge(channelId, sameVersionChallenge, new State[](0), sameVersionChallengerSig);
+
+ // 4. Create a new challenge state with a higher version number
+ State memory higherVersionChallenge = initialState;
+ higherVersionChallenge.intent = StateIntent.OPERATE;
+ higherVersionChallenge.data = abi.encode(44); // Different data
+ higherVersionChallenge.version = 98; // Higher version than the previous challenge (97)
+
+ // Host signs the higher version challenge
+ bytes memory hostHigherVersionSig = signState(chan, higherVersionChallenge, hostSKPrivKey);
+ bytes[] memory higherVersionSigs = new bytes[](1);
+ higherVersionSigs[0] = hostHigherVersionSig;
+ higherVersionChallenge.sigs = higherVersionSigs;
+ bytes memory higherVersionChallengerSig = signChallenge(chan, higherVersionChallenge, hostSKPrivKey);
+
+ // 5. Try to challenge with the higher version - must revert
+ vm.prank(hostSK);
+ vm.expectRevert(Custody.InvalidStatus.selector);
+ custody.challenge(channelId, higherVersionChallenge, new State[](0), higherVersionChallengerSig);
+ }
+
+ function test_revert_whenInvalidState() public {
+ // 1. Try to challenge with invalid state (adjudicator rejects)
+ State memory invalidState = initialState;
+ invalidState.intent = StateIntent.OPERATE;
+ invalidState.data = abi.encode(42);
+ invalidState.version = 97; // Version 97 indicates a challenge state (but will be rejected)
+ adjudicator.setAdjudicateReturnValue(false); // Set adjudicate return value to false for invalid state
+
+ // Host signs the invalid state
+ bytes memory hostInvalidSig = signState(chan, invalidState, hostSKPrivKey);
+ bytes[] memory invalidSigs = new bytes[](1);
+ invalidSigs[0] = hostInvalidSig;
+ invalidState.sigs = invalidSigs;
+
+ // Attempt to challenge with invalid state
+ bytes memory hostInvalidChallengerSig = signChallenge(chan, invalidState, hostSKPrivKey);
+ vm.prank(hostSK);
+ vm.expectRevert(Custody.InvalidState.selector);
+ custody.challenge(channelId, invalidState, new State[](0), hostInvalidChallengerSig);
+
+ // 2. Try to challenge non-existent channel
+ bytes32 nonExistentChannelId = bytes32(uint256(1234));
+ adjudicator.setAdjudicateReturnValue(true); // Set flag back to true
+
+ bytes memory hostNonExistingChallengerSig = signChallenge(chan, invalidState, hostSKPrivKey);
+
+ vm.prank(hostSK);
+ vm.expectRevert(abi.encodeWithSelector(Custody.ChannelNotFound.selector, nonExistentChannelId));
+ custody.challenge(nonExistentChannelId, invalidState, new State[](0), hostNonExistingChallengerSig);
+ }
+
+ function test_revert_whenInvalidChallengerSig() public {
+ // 1. Create a challenge state
+ State memory challengeState = initialState;
+ challengeState.data = abi.encode(42);
+
+ // Host signs the challenge state
+ bytes memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
+ bytes[] memory challengeSigs = new bytes[](1);
+ challengeSigs[0] = hostChallengeSig;
+ challengeState.sigs = challengeSigs;
+
+ // 2. Non-participant tries to challenge with a signature from non-participant
+ bytes memory nonParticipantSig = signChallenge(chan, challengeState, nonParticipantPrivKey);
+
+ vm.prank(nonParticipant);
+ vm.expectRevert(Custody.InvalidChallengerSignature.selector);
+ custody.challenge(channelId, challengeState, new State[](0), nonParticipantSig);
+ }
+
+ function test_immediateClose_whenChallengingInitial() public {
+ (uint256 hostAvailableBefore, uint256 hostChannelCountBefore) =
+ getAvailableBalanceAndChannelCount(hostSK, address(token));
+ (, uint256 guestChannelCountBefore) = getAvailableBalanceAndChannelCount(guestSK, address(token));
+
+ // Create a new channel for this test (guest doesn't join)
+ Channel memory testChan = createTestChannelWithSK();
+ testChan.nonce += 1; // Increment nonce to avoid conflicts
+ State memory testInitialState = createInitialStateWithSK();
+
+ // Set up signatures
+ bytes memory hostSig = signState(testChan, testInitialState, hostSKPrivKey);
+ bytes[] memory sigs = new bytes[](1);
+ sigs[0] = hostSig;
+ testInitialState.sigs = sigs;
+
+ // Create channel with host
+ uint256 hostDepositAmount = DEPOSIT_AMOUNT * 2;
+ depositTokens(hostSK, hostDepositAmount);
+ vm.prank(hostSK);
+ bytes32 testChannelId = custody.create(testChan, testInitialState);
+
+ // Guest does NOT join the channel
+ // Host challenges with initial state
+ bytes memory hostChallengerSig = signChallenge(testChan, testInitialState, hostSKPrivKey);
+ vm.prank(hostSK);
+ custody.challenge(testChannelId, testInitialState, new State[](0), hostChallengerSig);
+
+ // verify channel is immediately closed and funds distributed
+ (uint256 hostAvailable, uint256 hostChannelCount) = getAvailableBalanceAndChannelCount(hostSK, address(token));
+ (, uint256 guestChannelCount) = getAvailableBalanceAndChannelCount(guestSK, address(token));
+
+ assertEq(hostChannelCount, hostChannelCountBefore, "Host should have no channels after challenge");
+ assertEq(guestChannelCount, guestChannelCountBefore, "Guest should have no channels after challenge");
+
+ assertEq(hostAvailable, hostAvailableBefore + hostDepositAmount, "Host's available balance incorrect");
+ }
+}
+
+contract CustodyTest is CustodyTest_Base {
// ==================== TEST CASES ====================
// ==== 1. Channel Creation and Joining ====
@@ -250,8 +522,8 @@ contract CustodyTest is Test {
vm.deal(hostSK, 1 ether); // Ensure host has ETH for gas
// Sign the state
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory sigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory sigs = new bytes[](1);
sigs[0] = hostSig;
initialState.sigs = sigs;
@@ -279,8 +551,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// 2. Sign the state by the host
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory sigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory sigs = new bytes[](1);
sigs[0] = hostSig;
initialState.sigs = sigs;
@@ -304,8 +576,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// 2. Sign the state by the host
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -315,7 +587,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// 4. Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
vm.prank(guestSK);
@@ -348,8 +620,8 @@ contract CustodyTest is Test {
});
State memory initialState = createInitialStateWithSK();
- Signature memory hostSig = signState(chanWithInvalidParticipants, initialState, hostSKPrivKey);
- Signature[] memory sigs = new Signature[](1);
+ bytes memory hostSig = signState(chanWithInvalidParticipants, initialState, hostSKPrivKey);
+ bytes[] memory sigs = new bytes[](1);
sigs[0] = hostSig;
initialState.sigs = sigs;
@@ -395,8 +667,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// Host signs initial state
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -406,7 +678,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestSK, DEPOSIT_AMOUNT);
vm.prank(guestSK);
custody.join(channelId, 1, guestSig);
@@ -419,7 +691,7 @@ contract CustodyTest is Test {
hostSig = signState(chan, finalState, hostSKPrivKey);
guestSig = signState(chan, finalState, guestSKPrivKey);
- Signature[] memory bothSigs = new Signature[](2);
+ bytes[] memory bothSigs = new bytes[](2);
bothSigs[0] = hostSig;
bothSigs[1] = guestSig;
finalState.sigs = bothSigs;
@@ -445,8 +717,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -456,7 +728,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
vm.prank(guestSK);
custody.join(channelId, 1, guestSig);
@@ -464,7 +736,7 @@ contract CustodyTest is Test {
// 2. Try to close with invalid close state (missing CHANCLOSE magic number)
State memory invalidState = initialState; // Not a closing state
- Signature[] memory bothSigs = new Signature[](2);
+ bytes[] memory bothSigs = new bytes[](2);
bothSigs[0] = hostSig;
bothSigs[1] = guestSig;
invalidState.sigs = bothSigs;
@@ -485,265 +757,7 @@ contract CustodyTest is Test {
vm.stopPrank();
}
- // ==== 3. Challenge Mechanism ====
-
- function test_RejectChallengeDuringChallenge() public {
- // 1. Create and fund a channel
- Channel memory chan = createTestChannelWithSK();
- State memory initialState = createInitialStateWithSK();
-
- // Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
- hostSigs[0] = hostSig;
- initialState.sigs = hostSigs;
-
- // Create channel with host
- depositTokens(hostSK, DEPOSIT_AMOUNT * 2);
- vm.prank(hostSK);
- bytes32 channelId = custody.create(chan, initialState);
-
- // Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
- depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
- vm.prank(guestSK);
- custody.join(channelId, 1, guestSig);
-
- // 2. Create and submit first challenge state
- State memory challengeState = initialState;
- challengeState.intent = StateIntent.OPERATE;
- challengeState.data = abi.encode(42);
- challengeState.version = 97; // Version 97 indicates a challenge state
-
- // Host signs the challenge state
- Signature memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
- Signature[] memory challengeSigs = new Signature[](1);
- challengeSigs[0] = hostChallengeSig;
- challengeState.sigs = challengeSigs;
- Signature memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
-
- // Submit first challenge
- vm.prank(hostSK);
- custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
-
- // 3. Create a new challenge state with the same version number
- State memory sameVersionChallenge = initialState;
- sameVersionChallenge.intent = StateIntent.OPERATE;
- sameVersionChallenge.data = abi.encode(43); // Different data but same version
- sameVersionChallenge.version = 97; // Same version as previous challenge (97)
-
- // Host signs the same version challenge
- Signature memory hostSameVersionSig = signState(chan, sameVersionChallenge, hostSKPrivKey);
- Signature[] memory sameVersionSigs = new Signature[](1);
- sameVersionSigs[0] = hostSameVersionSig;
- sameVersionChallenge.sigs = sameVersionSigs;
- Signature memory sameVersionChallengerSig = signChallenge(chan, sameVersionChallenge, hostSKPrivKey);
-
- // 4. Try to challenge with the same version - should revert
- vm.prank(hostSK);
- vm.expectRevert(Custody.InvalidStatus.selector);
- custody.challenge(channelId, sameVersionChallenge, new State[](0), sameVersionChallengerSig);
-
- // 5. Create a new challenge state with a higher version number
- State memory higherVersionChallenge = initialState;
- higherVersionChallenge.intent = StateIntent.OPERATE;
- higherVersionChallenge.data = abi.encode(44); // Different data
- higherVersionChallenge.version = 98; // Higher version than the previous challenge (97)
-
- // Host signs the higher version challenge
- Signature memory hostHigherVersionSig = signState(chan, higherVersionChallenge, hostSKPrivKey);
- Signature[] memory higherVersionSigs = new Signature[](1);
- higherVersionSigs[0] = hostHigherVersionSig;
- higherVersionChallenge.sigs = higherVersionSigs;
- Signature memory higherVersionChallengerSig = signChallenge(chan, higherVersionChallenge, hostSKPrivKey);
-
- // 6. Try to challenge with the higher version - must revert
- vm.prank(hostSK);
- vm.expectRevert(Custody.InvalidStatus.selector);
- custody.challenge(channelId, higherVersionChallenge, new State[](0), higherVersionChallengerSig);
- }
-
- function test_Challenge() public {
- // 1. Create and fund a channel
- Channel memory chan = createTestChannelWithSK();
- State memory initialState = createInitialStateWithSK();
-
- // Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
- hostSigs[0] = hostSig;
- initialState.sigs = hostSigs;
-
- // Create channel with host
- depositTokens(hostSK, DEPOSIT_AMOUNT * 2);
- vm.prank(hostSK);
- bytes32 channelId = custody.create(chan, initialState);
-
- // Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
- depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
- vm.prank(guestSK);
- custody.join(channelId, 1, guestSig);
-
- // 2. Create a challenge state
- State memory challengeState = initialState;
- challengeState.intent = StateIntent.OPERATE;
- challengeState.data = abi.encode(42);
- challengeState.version = 97; // Version 97 indicates a challenge state
-
- // Host signs the challenge state
- Signature memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
- Signature[] memory challengeSigs = new Signature[](1);
- challengeSigs[0] = hostChallengeSig;
- challengeState.sigs = challengeSigs;
-
- // 3. Host challenges with this state and signs the challenge
- Signature memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
- vm.prank(hostSK);
- custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
-
- // 4. Skip time and close the channel
- skipChallengeTime();
-
- vm.prank(hostSK);
- custody.close(channelId, challengeState, new State[](0));
-
- // 5. Verify channel is closed and funds returned
- bytes32[] memory hostChannels = getAccountChannels(hostSK);
- assertEq(hostChannels.length, 0, "Host should have no channels after challenge resolution");
-
- (uint256 hostAvailable,) = getAvailableBalanceAndChannelCount(hostSK, address(token));
- (uint256 guestAvailable,) = getAvailableBalanceAndChannelCount(guestSK, address(token));
-
- assertEq(hostAvailable, DEPOSIT_AMOUNT * 2, "Host's available balance incorrect");
- assertEq(guestAvailable, DEPOSIT_AMOUNT * 2, "Guest's available balance incorrect");
- }
-
- function test_InvalidChallenge() public {
- // 1. Create and fund a channel
- Channel memory chan = createTestChannelWithSK();
- State memory initialState = createInitialStateWithSK();
-
- // Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
- hostSigs[0] = hostSig;
- initialState.sigs = hostSigs;
-
- // Create channel with host
- depositTokens(hostSK, DEPOSIT_AMOUNT * 2);
- vm.prank(hostSK);
- bytes32 channelId = custody.create(chan, initialState);
-
- // Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
- depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
- vm.prank(guestSK);
- custody.join(channelId, 1, guestSig);
-
- // 2. Try to challenge with invalid state (adjudicator rejects)
- State memory invalidState = initialState;
- invalidState.intent = StateIntent.OPERATE;
- invalidState.data = abi.encode(42);
- invalidState.version = 97; // Version 97 indicates a challenge state (but will be rejected)
- adjudicator.setAdjudicateReturnValue(false); // Set adjudicate return value to false for invalid state
-
- // Host signs the invalid state
- Signature memory hostInvalidSig = signState(chan, invalidState, hostSKPrivKey);
- Signature[] memory invalidSigs = new Signature[](1);
- invalidSigs[0] = hostInvalidSig;
- invalidState.sigs = invalidSigs;
-
- // Attempt to challenge with invalid state
- Signature memory hostInvalidChallengerSig = signChallenge(chan, invalidState, hostSKPrivKey);
- vm.prank(hostSK);
- vm.expectRevert(Custody.InvalidState.selector);
- custody.challenge(channelId, invalidState, new State[](0), hostInvalidChallengerSig);
-
- // 3. Try to challenge non-existent channel
- bytes32 nonExistentChannelId = bytes32(uint256(1234));
- adjudicator.setAdjudicateReturnValue(true); // Set flag back to true
-
- Signature memory hostNonExistingChallengerSig = signChallenge(chan, invalidState, hostSKPrivKey);
-
- vm.prank(hostSK);
- vm.expectRevert(abi.encodeWithSelector(Custody.ChannelNotFound.selector, nonExistentChannelId));
- custody.challenge(nonExistentChannelId, invalidState, new State[](0), hostNonExistingChallengerSig);
- }
-
- function test_InvalidChallengerSignature() public {
- // 1. Create and fund a channel
- Channel memory chan = createTestChannelWithSK();
- State memory initialState = createInitialStateWithSK();
-
- // Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
- hostSigs[0] = hostSig;
- initialState.sigs = hostSigs;
-
- // Create channel with host
- depositTokens(hostSK, DEPOSIT_AMOUNT * 2);
- vm.prank(hostSK);
- bytes32 channelId = custody.create(chan, initialState);
-
- // Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
- depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
- vm.prank(guestSK);
- custody.join(channelId, 1, guestSig);
-
- // 2. Create a challenge state
- State memory challengeState = initialState;
- challengeState.data = abi.encode(42);
-
- // Host signs the challenge state
- Signature memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
- Signature[] memory challengeSigs = new Signature[](1);
- challengeSigs[0] = hostChallengeSig;
- challengeState.sigs = challengeSigs;
-
- // 3. Non-participant tries to challenge with a signature from non-participant
- Signature memory nonParticipantSig = signChallenge(chan, challengeState, nonParticipantPrivKey);
-
- vm.prank(nonParticipant);
- vm.expectRevert(Custody.InvalidChallengerSignature.selector);
- custody.challenge(channelId, challengeState, new State[](0), nonParticipantSig);
- }
-
- function test_challengeInitial() public {
- // 1. Create and fund a channel
- Channel memory chan = createTestChannelWithSK();
- State memory initialState = createInitialStateWithSK();
-
- // Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
- hostSigs[0] = hostSig;
- initialState.sigs = hostSigs;
-
- // Create channel with host
- depositTokens(hostSK, DEPOSIT_AMOUNT * 2);
- vm.prank(hostSK);
- bytes32 channelId = custody.create(chan, initialState);
-
- // Guest does NOT join the channel
- // 2. Host challenges with initial state
- Signature memory hostChallengerSig = signChallenge(chan, initialState, hostSKPrivKey);
- vm.prank(hostSK);
- custody.challenge(channelId, initialState, new State[](0), hostChallengerSig);
-
- // verify channel is immediately closed and funds distributed
- (uint256 hostAvailable, uint256 hostChannelCount) = getAvailableBalanceAndChannelCount(hostSK, address(token));
- (, uint256 guestChannelCount) = getAvailableBalanceAndChannelCount(guestSK, address(token));
-
- assertEq(hostChannelCount, 0, "Host should have no channels after challenge");
- assertEq(guestChannelCount, 0, "Guest should have no channels after challenge");
-
- assertEq(hostAvailable, DEPOSIT_AMOUNT * 2, "Host's available balance incorrect");
- }
-
- // ==== 4. Checkpoint Mechanism ====
+ // ==== 3. Checkpoint Mechanism ====
function test_RejectEqualVersion() public {
// 1. Create and fund a channel
@@ -751,8 +765,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -762,7 +776,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
vm.prank(guestSK);
custody.join(channelId, 1, guestSig);
@@ -774,10 +788,10 @@ contract CustodyTest is Test {
checkpointState.version = 55; // Version 55 indicates a checkpoint state
// Both sign the checkpoint state
- Signature memory hostCheckpointSig = signState(chan, checkpointState, hostSKPrivKey);
- Signature memory guestCheckpointSig = signState(chan, checkpointState, guestSKPrivKey);
+ bytes memory hostCheckpointSig = signState(chan, checkpointState, hostSKPrivKey);
+ bytes memory guestCheckpointSig = signState(chan, checkpointState, guestSKPrivKey);
- Signature[] memory checkpointSigs = new Signature[](2);
+ bytes[] memory checkpointSigs = new bytes[](2);
checkpointSigs[0] = hostCheckpointSig;
checkpointSigs[1] = guestCheckpointSig;
checkpointState.sigs = checkpointSigs;
@@ -793,10 +807,10 @@ contract CustodyTest is Test {
sameVersionState.version = 55; // Same version as previous checkpoint (55)
// Both sign the new state
- Signature memory hostSameVersionSig = signState(chan, sameVersionState, hostSKPrivKey);
- Signature memory guestSameVersionSig = signState(chan, sameVersionState, guestSKPrivKey);
+ bytes memory hostSameVersionSig = signState(chan, sameVersionState, hostSKPrivKey);
+ bytes memory guestSameVersionSig = signState(chan, sameVersionState, guestSKPrivKey);
- Signature[] memory sameVersionSigs = new Signature[](2);
+ bytes[] memory sameVersionSigs = new bytes[](2);
sameVersionSigs[0] = hostSameVersionSig;
sameVersionSigs[1] = guestSameVersionSig;
sameVersionState.sigs = sameVersionSigs;
@@ -814,8 +828,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -825,7 +839,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
vm.prank(guestSK);
custody.join(channelId, 1, guestSig);
@@ -837,10 +851,10 @@ contract CustodyTest is Test {
checkpointState.version = 55; // Version 55 indicates a checkpoint state
// Both sign the checkpoint state
- Signature memory hostCheckpointSig = signState(chan, checkpointState, hostSKPrivKey);
- Signature memory guestCheckpointSig = signState(chan, checkpointState, guestSKPrivKey);
+ bytes memory hostCheckpointSig = signState(chan, checkpointState, hostSKPrivKey);
+ bytes memory guestCheckpointSig = signState(chan, checkpointState, guestSKPrivKey);
- Signature[] memory checkpointSigs = new Signature[](2);
+ bytes[] memory checkpointSigs = new bytes[](2);
checkpointSigs[0] = hostCheckpointSig;
checkpointSigs[1] = guestCheckpointSig;
checkpointState.sigs = checkpointSigs;
@@ -854,12 +868,12 @@ contract CustodyTest is Test {
challengeState.intent = StateIntent.OPERATE;
challengeState.data = abi.encode(21);
challengeState.version = 97; // Version 97 indicates a challenge state (higher than checkpoint's 55)
- Signature memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
- Signature[] memory challengeSigs = new Signature[](1);
+ bytes memory hostChallengeSig = signState(chan, challengeState, hostSKPrivKey);
+ bytes[] memory challengeSigs = new bytes[](1);
challengeSigs[0] = hostChallengeSig;
challengeState.sigs = challengeSigs;
- Signature memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
+ bytes memory hostChallengerSig = signChallenge(chan, challengeState, hostSKPrivKey);
vm.prank(hostSK);
custody.challenge(channelId, challengeState, new State[](0), hostChallengerSig);
@@ -870,10 +884,10 @@ contract CustodyTest is Test {
resolveState.version = 98; // Even higher version to resolve the challenge
// Both sign the resolve state
- Signature memory hostResolveSignature = signState(chan, resolveState, hostSKPrivKey);
- Signature memory guestResolveSignature = signState(chan, resolveState, guestSKPrivKey);
+ bytes memory hostResolveSignature = signState(chan, resolveState, hostSKPrivKey);
+ bytes memory guestResolveSignature = signState(chan, resolveState, guestSKPrivKey);
- Signature[] memory resolveSignatures = new Signature[](2);
+ bytes[] memory resolveSignatures = new bytes[](2);
resolveSignatures[0] = hostResolveSignature;
resolveSignatures[1] = guestResolveSignature;
resolveState.sigs = resolveSignatures;
@@ -885,9 +899,9 @@ contract CustodyTest is Test {
State memory closeState = createClosingStateWithSK();
closeState.version = 100; // Version 100 indicates a closing state
// Add signatures
- Signature memory hostCloseSig = signState(chan, closeState, hostSKPrivKey);
- Signature memory guestCloseSig = signState(chan, closeState, guestSKPrivKey);
- Signature[] memory closeSigs = new Signature[](2);
+ bytes memory hostCloseSig = signState(chan, closeState, hostSKPrivKey);
+ bytes memory guestCloseSig = signState(chan, closeState, guestSKPrivKey);
+ bytes[] memory closeSigs = new bytes[](2);
closeSigs[0] = hostCloseSig;
closeSigs[1] = guestCloseSig;
closeState.sigs = closeSigs;
@@ -896,7 +910,7 @@ contract CustodyTest is Test {
custody.close(channelId, closeState, new State[](0));
}
- // ==== 5. Fund Management ====
+ // ==== 4. Fund Management ====
function test_DepositAndWithdraw() public {
// 1. Test deposit
@@ -921,7 +935,7 @@ contract CustodyTest is Test {
vm.stopPrank();
}
- // ==== 6. Resize Function ====
+ // ==== 5. Resize Function ====
function test_Resize() public {
// 1. Create and fund a channel
@@ -929,8 +943,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithSK();
// Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -940,7 +954,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestSK, DEPOSIT_AMOUNT * 2);
vm.prank(guestSK);
custody.join(channelId, 1, guestSig);
@@ -958,13 +972,13 @@ contract CustodyTest is Test {
version: 41, // Version 41 indicates state before resize
data: abi.encode(41), // Simple application data
allocations: initialState.allocations,
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
// If adjudicator allows, then only one can sign the preceding state
- Signature memory hostPrecedingSig = signState(chan, precedingState, hostSKPrivKey);
+ bytes memory hostPrecedingSig = signState(chan, precedingState, hostSKPrivKey);
- Signature[] memory precedingSigs = new Signature[](1);
+ bytes[] memory precedingSigs = new bytes[](1);
precedingSigs[0] = hostPrecedingSig;
precedingState.sigs = precedingSigs;
@@ -981,7 +995,7 @@ contract CustodyTest is Test {
version: 42, // Version 42 indicates a resize state
data: abi.encode(resizeAmounts),
allocations: new Allocation[](2),
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
uint256 resizedHostLockedBalance = uint256(int256(DEPOSIT_AMOUNT) + resizeHostDelta);
@@ -992,10 +1006,10 @@ contract CustodyTest is Test {
resizedState.allocations[1].amount = resizedGuestLockedBalance; // Guest now has updated amount
// Both sign the resized state
- Signature memory hostResizeSig = signState(chan, resizedState, hostSKPrivKey);
- Signature memory guestResizeSig = signState(chan, resizedState, guestSKPrivKey);
+ bytes memory hostResizeSig = signState(chan, resizedState, hostSKPrivKey);
+ bytes memory guestResizeSig = signState(chan, resizedState, guestSKPrivKey);
- Signature[] memory resizeSigs = new Signature[](2);
+ bytes[] memory resizeSigs = new bytes[](2);
resizeSigs[0] = hostResizeSig;
resizeSigs[1] = guestResizeSig;
resizedState.sigs = resizeSigs;
@@ -1034,10 +1048,10 @@ contract CustodyTest is Test {
afterResizeState.allocations[1].amount = resizedGuestLockedBalance;
// Both sign the state after resize
- Signature memory hostAfterSig = signState(chan, afterResizeState, hostSKPrivKey);
- Signature memory guestAfterSig = signState(chan, afterResizeState, guestSKPrivKey);
+ bytes memory hostAfterSig = signState(chan, afterResizeState, hostSKPrivKey);
+ bytes memory guestAfterSig = signState(chan, afterResizeState, guestSKPrivKey);
- Signature[] memory afterSigs = new Signature[](2);
+ bytes[] memory afterSigs = new bytes[](2);
afterSigs[0] = hostAfterSig;
afterSigs[1] = guestAfterSig;
afterResizeState.sigs = afterSigs;
@@ -1085,7 +1099,7 @@ contract CustodyTest is Test {
);
}
- // ==== 7. Implicit Transfer in Resize ====
+ // ==== 6. Implicit Transfer in Resize ====
/**
* @notice Test case for basic implicit transfer functionality
@@ -1097,8 +1111,8 @@ contract CustodyTest is Test {
State memory initialState = createInitialStateWithWallets(); // wallets are depositors and destinations in allocations
// Set up signatures
- Signature memory hostSig = signState(chan, initialState, hostSKPrivKey);
- Signature[] memory hostSigs = new Signature[](1);
+ bytes memory hostSig = signState(chan, initialState, hostSKPrivKey);
+ bytes[] memory hostSigs = new bytes[](1);
hostSigs[0] = hostSig;
initialState.sigs = hostSigs;
@@ -1108,7 +1122,7 @@ contract CustodyTest is Test {
bytes32 channelId = custody.create(chan, initialState);
// Guest joins the channel
- Signature memory guestSig = signState(chan, initialState, guestSKPrivKey);
+ bytes memory guestSig = signState(chan, initialState, guestSKPrivKey);
depositTokens(guestWallet, DEPOSIT_AMOUNT * 2);
vm.prank(guestWallet);
custody.join(channelId, 1, guestSig);
@@ -1119,12 +1133,12 @@ contract CustodyTest is Test {
version: 41, // Version 41 indicates state before resize
data: abi.encode(41), // Simple application data
allocations: initialState.allocations,
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
// Host signs the preceding state
- Signature memory hostPrecedingSig = signState(chan, precedingState, hostSKPrivKey);
- Signature[] memory precedingSigs = new Signature[](1);
+ bytes memory hostPrecedingSig = signState(chan, precedingState, hostSKPrivKey);
+ bytes[] memory precedingSigs = new bytes[](1);
precedingSigs[0] = hostPrecedingSig;
precedingState.sigs = precedingSigs;
@@ -1145,7 +1159,7 @@ contract CustodyTest is Test {
version: 42, // Version 42 indicates a resize state
data: abi.encode(resizeAmounts),
allocations: new Allocation[](2),
- sigs: new Signature[](0) // Empty initially
+ sigs: new bytes[](0) // Empty initially
});
// Calculate new allocation amounts
@@ -1163,10 +1177,10 @@ contract CustodyTest is Test {
});
// Both sign the resized state
- Signature memory hostResizeSig = signState(chan, resizedState, hostSKPrivKey);
- Signature memory guestResizeSig = signState(chan, resizedState, guestSKPrivKey);
+ bytes memory hostResizeSig = signState(chan, resizedState, hostSKPrivKey);
+ bytes memory guestResizeSig = signState(chan, resizedState, guestSKPrivKey);
- Signature[] memory resizeSigs = new Signature[](2);
+ bytes[] memory resizeSigs = new bytes[](2);
resizeSigs[0] = hostResizeSig;
resizeSigs[1] = guestResizeSig;
resizedState.sigs = resizeSigs;
@@ -1188,7 +1202,7 @@ contract CustodyTest is Test {
assertEq(guestAvailableAfterResize, 3 * DEPOSIT_AMOUNT, "Guest should have their 3 * deposit available");
}
- // ==== 8. Separate Depositor and Participant Addresses ====
+ // ==== 7. Separate Depositor and Participant Addresses ====
function test_SeparateDepositorAndParticipant() public {
// 1. Prepare channel with different participant addresses
@@ -1198,8 +1212,8 @@ contract CustodyTest is Test {
initialState.allocations[0].destination = depositor;
// 2. Sign the state by the host participant (not the depositor/creator)
- Signature memory hostPartSig = signState(chan, initialState, hostWalletPrivKey);
- Signature[] memory sigs = new Signature[](1);
+ bytes memory hostPartSig = signState(chan, initialState, hostWalletPrivKey);
+ bytes[] memory sigs = new bytes[](1);
sigs[0] = hostPartSig;
initialState.sigs = sigs;
@@ -1228,7 +1242,7 @@ contract CustodyTest is Test {
vm.stopPrank();
// Sign the state by guest participant
- Signature memory guestPartSig = signState(chan, initialState, guestWalletPrivKey);
+ bytes memory guestPartSig = signState(chan, initialState, guestWalletPrivKey);
// Guest participant joins with their own signature
vm.prank(guestWallet);
@@ -1251,10 +1265,10 @@ contract CustodyTest is Test {
checkpointState.version = 55; // Version 55 indicates a checkpoint state
// Both participants sign the checkpoint state
- Signature memory hostPartCheckpointSig = signState(chan, checkpointState, hostWalletPrivKey);
- Signature memory guestPartCheckpointSig = signState(chan, checkpointState, guestWalletPrivKey);
+ bytes memory hostPartCheckpointSig = signState(chan, checkpointState, hostWalletPrivKey);
+ bytes memory guestPartCheckpointSig = signState(chan, checkpointState, guestWalletPrivKey);
- Signature[] memory checkpointSigs = new Signature[](2);
+ bytes[] memory checkpointSigs = new bytes[](2);
checkpointSigs[0] = hostPartCheckpointSig;
checkpointSigs[1] = guestPartCheckpointSig;
checkpointState.sigs = checkpointSigs;
@@ -1269,10 +1283,10 @@ contract CustodyTest is Test {
finalState.allocations[0].destination = depositor;
// Both participants sign the final state
- Signature memory hostPartFinalSig = signState(chan, finalState, hostWalletPrivKey);
- Signature memory guestPartFinalSig = signState(chan, finalState, guestWalletPrivKey);
+ bytes memory hostPartFinalSig = signState(chan, finalState, hostWalletPrivKey);
+ bytes memory guestPartFinalSig = signState(chan, finalState, guestWalletPrivKey);
- Signature[] memory finalSigs = new Signature[](2);
+ bytes[] memory finalSigs = new bytes[](2);
finalSigs[0] = hostPartFinalSig;
finalSigs[1] = guestPartFinalSig;
finalState.sigs = finalSigs;
diff --git a/contract/test/Custody_integration.t.sol b/contract/test/Custody_integration.t.sol
new file mode 100644
index 000000000..753e31409
--- /dev/null
+++ b/contract/test/Custody_integration.t.sol
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {Test} from "lib/forge-std/src/Test.sol";
+import {Vm} from "lib/forge-std/src/Vm.sol";
+
+import {TestUtils} from "./TestUtils.sol";
+import {MockERC20} from "./mocks/MockERC20.sol";
+import {MockFlagERC1271} from "./mocks/MockFlagERC1271.sol";
+
+import {Custody} from "../src/Custody.sol";
+import {SimpleConsensus} from "../src/adjudicators/SimpleConsensus.sol";
+import {Utils} from "../src/Utils.sol";
+import {ChannelStatus, Channel, State, Allocation, StateIntent, STATE_TYPEHASH} from "../src/interfaces/Types.sol";
+
+/// @dev used to mock the deployment of ERC-4337 accounts to specific addresses
+contract CheatERC6492Factory is Test {
+ function createAccount(address to, bool flag) external {
+ deployCodeTo("MockFlagERC1271", abi.encode(flag), to);
+ }
+}
+
+contract CustodyIntegrationTest_Signatures is Test {
+ Custody public custody;
+ SimpleConsensus public adjudicator;
+ MockERC20 public token;
+ CheatERC6492Factory public factory;
+
+ // Test participants
+ address public participant1;
+ address public participant2;
+ uint256 public participant1PrivateKey;
+ uint256 public participant2PrivateKey;
+
+ // Test parameters
+ uint256 constant DEPOSIT_AMOUNT = 1000;
+ uint256 constant INITIAL_BALANCE = 10000;
+ uint64 constant CHALLENGE_DURATION = 3600; // 1 hour
+ uint64 constant NONCE = 1;
+
+ // Channel and state tracking
+ Channel public channel;
+ bytes32 public channelId;
+
+ // Constants for participant ordering
+ uint256 private constant PARTICIPANT_1 = 0;
+ uint256 private constant PARTICIPANT_2 = 1;
+
+ function setUp() public {
+ // Deploy contracts
+ custody = new Custody();
+ adjudicator = new SimpleConsensus(address(this), address(custody));
+ token = new MockERC20("Test Token", "TEST", 18);
+ factory = new CheatERC6492Factory();
+
+ // Set up participants
+ participant1PrivateKey = vm.createWallet("participant1").privateKey;
+ participant2PrivateKey = vm.createWallet("participant2").privateKey;
+ participant1 = vm.addr(participant1PrivateKey);
+ participant2 = vm.addr(participant2PrivateKey);
+
+ // Fund participants
+ token.mint(participant1, INITIAL_BALANCE);
+ token.mint(participant2, INITIAL_BALANCE);
+
+ // Approve token transfers
+ vm.prank(participant1);
+ token.approve(address(custody), INITIAL_BALANCE);
+
+ vm.prank(participant2);
+ token.approve(address(custody), INITIAL_BALANCE);
+
+ // Create channel
+ address[] memory participants = new address[](2);
+ participants[PARTICIPANT_1] = participant1;
+ participants[PARTICIPANT_2] = participant2;
+
+ channel = Channel({
+ participants: participants,
+ adjudicator: address(adjudicator),
+ challenge: CHALLENGE_DURATION,
+ nonce: NONCE
+ });
+
+ channelId = Utils.getChannelId(channel);
+ }
+
+ // ==================== SIGNATURE HELPERS ====================
+
+ function _signStateRaw(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedState = Utils.getPackedState(channelId, state);
+ return TestUtils.sign(vm, privateKey, packedState);
+ }
+
+ function _signStateEIP191(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedState = Utils.getPackedState(channelId, state);
+ return TestUtils.signEIP191(vm, privateKey, packedState);
+ }
+
+ function _signStateEIP712(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ (, string memory name, string memory version, uint256 chainId, address verifyingContract,,) =
+ custody.eip712Domain();
+ bytes32 domainSeparator = keccak256(
+ abi.encode(
+ keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
+ keccak256(bytes(name)),
+ keccak256(bytes(version)),
+ chainId,
+ verifyingContract
+ )
+ );
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ state.intent,
+ state.version,
+ keccak256(state.data),
+ keccak256(abi.encode(state.allocations))
+ )
+ );
+ return TestUtils.signEIP712(vm, privateKey, domainSeparator, structHash);
+ }
+
+ function _signStateEIP6492(address signer, State memory) internal view returns (bytes memory) {
+ bytes memory signature = "dummy signature";
+ bool flag = true; // meaning each EIP-1271 signature is valid
+
+ bytes memory factoryCalldata = abi.encodeWithSelector(CheatERC6492Factory.createAccount.selector, signer, flag);
+
+ bytes memory erc6492Sig = abi.encode(address(factory), factoryCalldata, signature);
+ return abi.encodePacked(erc6492Sig, Utils.ERC6492_DETECTION_SUFFIX);
+ }
+
+ function _signChallenge(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedChallengeState = abi.encodePacked(Utils.getPackedState(channelId, state), "challenge");
+ return TestUtils.sign(vm, privateKey, packedChallengeState);
+ }
+
+ // ==================== STATE CREATION HELPERS ====================
+
+ function _createState(StateIntent intent, uint256 version, bytes memory data, uint256 amount1, uint256 amount2)
+ internal
+ view
+ returns (State memory)
+ {
+ Allocation[] memory allocations = new Allocation[](2);
+ allocations[PARTICIPANT_1] = Allocation({destination: participant1, token: address(token), amount: amount1});
+ allocations[PARTICIPANT_2] = Allocation({destination: participant2, token: address(token), amount: amount2});
+
+ return State({intent: intent, version: version, data: data, allocations: allocations, sigs: new bytes[](0)});
+ }
+
+ function _createInitialState() internal view returns (State memory) {
+ return _createState(StateIntent.INITIALIZE, 0, bytes(""), DEPOSIT_AMOUNT, DEPOSIT_AMOUNT);
+ }
+
+ function _createOperateState(uint256 version, bytes memory data) internal view returns (State memory) {
+ return _createState(StateIntent.OPERATE, version, data, DEPOSIT_AMOUNT, DEPOSIT_AMOUNT);
+ }
+
+ function _createFinalState(uint256 version) internal view returns (State memory) {
+ return _createState(StateIntent.FINALIZE, version, bytes(""), DEPOSIT_AMOUNT, DEPOSIT_AMOUNT);
+ }
+
+ // ==================== MAIN INTEGRATION TEST ====================
+
+ function test_fullChannelLifecycle_withMixedSignatures() public {
+ // ==================== 1. CREATE CHANNEL ====================
+
+ // Create initial state - participant1 uses EIP191
+ State memory initialState = _createInitialState();
+ initialState.sigs = new bytes[](1);
+ initialState.sigs[0] = _signStateEIP191(initialState, participant1PrivateKey);
+
+ vm.prank(participant1);
+ custody.depositAndCreate(address(token), DEPOSIT_AMOUNT, channel, initialState);
+
+ (, ChannelStatus status,,,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.INITIAL, "Channel should be in INITIAL status");
+
+ // ==================== 2. JOIN CHANNEL ====================
+
+ vm.prank(participant2);
+ custody.deposit(participant2, address(token), DEPOSIT_AMOUNT);
+
+ // Participant2 joins using raw ECDSA signature
+ bytes memory participant2JoinSig = _signStateRaw(initialState, participant2PrivateKey);
+
+ vm.prank(participant2);
+ custody.join(channelId, 1, participant2JoinSig);
+
+ (, status,,,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.ACTIVE, "Channel should be in ACTIVE status");
+
+ // ==================== 3. CHALLENGE CHANNEL ====================
+
+ // Create challenge state - participant1 uses EIP712, participant2 uses raw ECDSA
+ State memory challengeState = _createOperateState(1, bytes("challenge data"));
+ challengeState.sigs = new bytes[](2);
+ challengeState.sigs[PARTICIPANT_1] = _signStateEIP712(challengeState, participant1PrivateKey);
+ challengeState.sigs[PARTICIPANT_2] = _signStateRaw(challengeState, participant2PrivateKey);
+
+ bytes memory challengerSig = _signChallenge(challengeState, participant1PrivateKey);
+
+ vm.prank(participant1);
+ custody.challenge(channelId, challengeState, new State[](0), challengerSig);
+
+ uint256 challengeExpiry;
+ (, status,, challengeExpiry,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.DISPUTE, "Channel should be in DISPUTE status");
+ assertTrue(challengeExpiry > block.timestamp, "Channel should have challengeExpiry set in future");
+
+ // ==================== 4. CHECKPOINT TO RESOLVE CHALLENGE ====================
+
+ // Create checkpoint state with higher version - participant1 uses raw ECDSA, participant2 uses raw ECDSA
+ State memory checkpointState = _createOperateState(2, bytes("checkpoint data"));
+ checkpointState.sigs = new bytes[](2);
+ checkpointState.sigs[PARTICIPANT_1] = _signStateRaw(checkpointState, participant1PrivateKey);
+ checkpointState.sigs[PARTICIPANT_2] = _signStateRaw(checkpointState, participant2PrivateKey);
+
+ vm.prank(participant2);
+ custody.checkpoint(channelId, checkpointState, new State[](0));
+
+ (, status,, challengeExpiry,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.ACTIVE, "Channel should be back to ACTIVE status after checkpoint");
+ assertEq(challengeExpiry, 0, "Channel should have no challengeExpiry after checkpoint");
+
+ // ==================== 5. CHECKPOINT AGAIN ====================
+
+ // Create checkpoint state with higher version - participant1 uses EIP-6492, participant2 uses raw ECDSA
+ checkpointState = _createOperateState(3, bytes("checkpoint data"));
+ checkpointState.sigs = new bytes[](2);
+ checkpointState.sigs[PARTICIPANT_1] = _signStateEIP6492(participant1, checkpointState);
+ checkpointState.sigs[PARTICIPANT_2] = _signStateRaw(checkpointState, participant2PrivateKey);
+
+ vm.prank(participant2);
+ custody.checkpoint(channelId, checkpointState, new State[](0));
+
+ (, status,, challengeExpiry,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.ACTIVE, "Channel should still be in an ACTIVE status after checkpoint");
+
+ // ==================== 6. CLOSE CHANNEL ====================
+
+ // Create final state - participant1 uses EIP1271, participant2 uses raw ECDSA
+ State memory finalState = _createFinalState(4);
+ finalState.sigs = new bytes[](2);
+ // as participant1 already has a contract at its address, we assume this contract expects EIP-191 signature
+ finalState.sigs[PARTICIPANT_1] = _signStateEIP191(finalState, participant1PrivateKey);
+ finalState.sigs[PARTICIPANT_2] = _signStateRaw(finalState, participant2PrivateKey);
+
+ vm.prank(participant1);
+ custody.close(channelId, finalState, new State[](0));
+
+ (, status,,,) = custody.getChannelData(channelId);
+
+ assertTrue(status == ChannelStatus.VOID, "Channel should have VOID status after close (channel data deleted)");
+
+ // ==================== 7. VERIFY FINAL BALANCES ====================
+
+ address[] memory users = new address[](2);
+ users[0] = participant1;
+ users[1] = participant2;
+
+ address[] memory tokens = new address[](1);
+ tokens[0] = address(token);
+
+ uint256[][] memory balances = custody.getAccountsBalances(users, tokens);
+
+ assertEq(balances[0][0], DEPOSIT_AMOUNT, "Participant1 should have deposit amount available");
+ assertEq(balances[1][0], DEPOSIT_AMOUNT, "Participant2 should have deposit amount available");
+
+ bytes32[][] memory channels = custody.getOpenChannels(users);
+ assertEq(channels[0].length, 0, "Participant1 should have no open channels");
+ assertEq(channels[1].length, 0, "Participant2 should have no open channels");
+ }
+
+ // ==================== HELPER FUNCTION FOR VERIFICATION ====================
+
+ function skipChallengeTime() internal {
+ skip(CHALLENGE_DURATION + 1);
+ }
+}
diff --git a/contract/test/TestUtils.sol b/contract/test/TestUtils.sol
index 11275129b..733a976c0 100644
--- a/contract/test/TestUtils.sol
+++ b/contract/test/TestUtils.sol
@@ -1,11 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
+import {EIP712} from "lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol";
+
import {Vm} from "lib/forge-std/src/Vm.sol";
+import {MessageHashUtils} from "lib/openzeppelin-contracts/contracts/utils/cryptography/MessageHashUtils.sol";
+
+import {State, Channel} from "../src/interfaces/Types.sol";
+import {Utils} from "../src/Utils.sol";
library TestUtils {
- function sign(Vm vm, uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s) {
- // Sign the digest directly without applying EIP-191 prefix
- (v, r, s) = vm.sign(privateKey, digest);
+ bytes32 public constant TYPE_HASH =
+ keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
+
+ function buildDomainSeparator(string memory name, string memory version, uint256 chainId, address verifyingContract)
+ internal
+ pure
+ returns (bytes32)
+ {
+ return keccak256(
+ abi.encode(TYPE_HASH, keccak256(bytes(name)), keccak256(bytes(version)), chainId, verifyingContract)
+ );
+ }
+
+ function buildDomainSeparatorForContract(EIP712 eip712Contract) internal view returns (bytes32) {
+ (, string memory name, string memory version, uint256 chainId, address verifyingContract,,) =
+ eip712Contract.eip712Domain();
+ return buildDomainSeparator(name, version, chainId, verifyingContract);
+ }
+
+ function sign(Vm vm, uint256 privateKey, bytes memory message) internal pure returns (bytes memory) {
+ // Sign the message directly without applying EIP-191 prefix
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, keccak256(message));
+ return abi.encodePacked(r, s, v);
+ }
+
+ function signEIP191(Vm vm, uint256 privateKey, bytes memory message) internal pure returns (bytes memory) {
+ // Apply EIP-191 prefix and sign
+ bytes32 ethSignedMessageHash = MessageHashUtils.toEthSignedMessageHash(message);
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, ethSignedMessageHash);
+ return abi.encodePacked(r, s, v);
+ }
+
+ function signEIP712(Vm vm, uint256 privateKey, bytes32 domainSeparator, bytes32 structHash)
+ internal
+ pure
+ returns (bytes memory)
+ {
+ // Apply EIP-712 prefix and sign
+ bytes32 typedDataHash = MessageHashUtils.toTypedDataHash(domainSeparator, structHash);
+ (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, typedDataHash);
+ return abi.encodePacked(r, s, v);
+ }
+
+ function signStateEIP191(Vm vm, Channel memory channel, State memory state, uint256 privateKey)
+ internal
+ view
+ returns (bytes memory)
+ {
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(channel), state);
+ return TestUtils.signEIP191(vm, privateKey, packedState);
+ }
+
+ function signStateEIP712(
+ Vm vm,
+ bytes32 channelId,
+ State memory state,
+ bytes32 stateTypehash,
+ bytes32 domainSeparator,
+ uint256 privateKey
+ ) internal pure returns (bytes memory) {
+ bytes32 structHash = keccak256(
+ abi.encode(
+ stateTypehash,
+ channelId,
+ state.intent,
+ state.version,
+ keccak256(state.data),
+ keccak256(abi.encode(state.allocations))
+ )
+ );
+ return TestUtils.signEIP712(vm, privateKey, domainSeparator, structHash);
}
}
diff --git a/contract/test/Utils.t.sol b/contract/test/Utils.t.sol
new file mode 100644
index 000000000..9c8a6ec68
--- /dev/null
+++ b/contract/test/Utils.t.sol
@@ -0,0 +1,536 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {Test} from "lib/forge-std/src/Test.sol";
+
+import {MockEIP712} from "./mocks/MockEIP712.sol";
+import {MockERC20} from "./mocks/MockERC20.sol";
+import {MockFlagERC1271} from "./mocks/MockFlagERC1271.sol";
+import {MockERC4337Factory} from "./mocks/MockERC4337Factory.sol";
+import {TestUtils} from "./TestUtils.sol";
+import {UtilsHarness} from "./UtilsHarness.sol";
+
+import {Channel, State, Allocation, StateIntent, STATE_TYPEHASH} from "../src/interfaces/Types.sol";
+import {Utils} from "../src/Utils.sol";
+
+abstract contract UtilsTest_SignaturesBase is Test {
+ UtilsHarness public utils;
+ MockEIP712 public mockEIP712;
+ MockERC4337Factory public erc4337Factory;
+ bytes32 public domainSeparator;
+
+ address public signer;
+ uint256 public signerPrivateKey;
+ address public wrongSigner;
+ uint256 public wrongSignerPrivateKey;
+
+ function setUp() public virtual {
+ utils = new UtilsHarness();
+ mockEIP712 = new MockEIP712("TestDomain", "1.0");
+ erc4337Factory = new MockERC4337Factory();
+ domainSeparator = mockEIP712.domainSeparator();
+
+ signerPrivateKey = vm.createWallet("signer").privateKey;
+ wrongSignerPrivateKey = vm.createWallet("wrongSigner").privateKey;
+ signer = vm.addr(signerPrivateKey);
+ wrongSigner = vm.addr(wrongSignerPrivateKey);
+ }
+
+ function getERC6492SignatureAndSigner(bool flag, bytes32 salt, bytes memory originalSig)
+ internal
+ view
+ virtual
+ returns (bytes memory, address)
+ {
+ address expectedSigner = erc4337Factory.getAddress(flag, salt);
+ bytes memory factoryCalldata = abi.encodeWithSelector(MockERC4337Factory.createAccount.selector, flag, salt);
+
+ bytes memory erc6492Sig = abi.encode(address(erc4337Factory), factoryCalldata, originalSig);
+ bytes memory sigWithSuffix = abi.encodePacked(erc6492Sig, Utils.ERC6492_DETECTION_SUFFIX);
+
+ return (sigWithSuffix, expectedSigner);
+ }
+}
+
+contract UtilsTest_Signatures is UtilsTest_SignaturesBase {
+ bytes message = "test message";
+ bytes32 messageHash = keccak256(message);
+
+ // ==================== recoverRawECDSASigner TESTS ====================
+
+ function test_recoverRawECDSASigner_returnsCorrectSigner() public view {
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, message);
+
+ address recoveredSigner = utils.recoverRawECDSASigner(message, sig);
+
+ assertEq(recoveredSigner, signer, "Should recover correct signer");
+ }
+
+ function test_recoverRawECDSASigner_returnsWrongSignerForDifferentMessage() public view {
+ bytes memory differentMessage = "different message";
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, message);
+
+ address recoveredSigner = utils.recoverRawECDSASigner(differentMessage, sig);
+
+ assertNotEq(recoveredSigner, signer, "Should not recover correct signer for different message");
+ }
+
+ // ==================== recoverEIP191Signer TESTS ====================
+
+ function test_recoverEIP191Signer_returnsCorrectSigner() public view {
+ bytes memory sig = TestUtils.signEIP191(vm, signerPrivateKey, message);
+
+ address recoveredSigner = utils.recoverEIP191Signer(message, sig);
+
+ assertEq(recoveredSigner, signer, "Should recover correct signer with EIP191");
+ }
+
+ function test_recoverEIP191Signer_returnsWrongSigner_forDifferentMessage() public view {
+ bytes memory differentMessage = "different message";
+ bytes memory sig = TestUtils.signEIP191(vm, signerPrivateKey, message);
+
+ address recoveredSigner = utils.recoverEIP191Signer(differentMessage, sig);
+
+ assertNotEq(recoveredSigner, signer, "Should not recover correct signer for different message");
+ }
+
+ function test_recoverEIP191Signer_returnsWrongSigner_forRawSignature() public view {
+ // Sign with raw ECDSA instead of EIP191
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, message);
+
+ address recoveredSigner = utils.recoverEIP191Signer(message, sig);
+
+ assertNotEq(recoveredSigner, signer, "Should not recover correct signer when using raw signature for EIP191");
+ }
+
+ // ==================== recoverEIP712Signer TESTS ====================
+
+ function test_recoverEIP712Signer_returnsCorrectSigner() public view {
+ bytes32 structHash = keccak256("test struct");
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ address recoveredSigner = utils.recoverEIP712Signer(domainSeparator, structHash, sig);
+
+ assertEq(recoveredSigner, signer, "Should recover correct signer with EIP712");
+ }
+
+ function test_recoverEIP712Signer_returnsWrongSignerForDifferentDomain() public view {
+ bytes32 differentDomainSeparator = keccak256("different domain");
+ bytes32 structHash = keccak256("test struct");
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ address recoveredSigner = utils.recoverEIP712Signer(differentDomainSeparator, structHash, sig);
+
+ assertNotEq(recoveredSigner, signer, "Should not recover correct signer for different domain");
+ }
+
+ // ==================== isValidERC1271Signature TESTS ====================
+
+ function test_isValidERC1271Signature_returnsTrue_forValidSignature() public {
+ MockFlagERC1271 mockContract = new MockFlagERC1271(true);
+ bytes memory sig = "dummy signature";
+
+ bool isValid = utils.isValidERC1271Signature(messageHash, sig, address(mockContract));
+
+ assertTrue(isValid, "Should return true when flag is true");
+ }
+
+ function test_isValidERC1271Signature_returnsFalse_forInvalidSignature() public {
+ MockFlagERC1271 mockContract = new MockFlagERC1271(false);
+ bytes memory sig = "dummy signature";
+
+ bool isValid = utils.isValidERC1271Signature(messageHash, sig, address(mockContract));
+
+ assertFalse(isValid, "Should return false when flag is false");
+ }
+
+ // ==================== isValidERC6492Signature TESTS ====================
+
+ function test_isValidERC6492Signature_returnsTrue_forValidSignature_notDeployed() public {
+ (bytes memory signature, address expectedSigner) =
+ getERC6492SignatureAndSigner(true, keccak256("test salt"), "dummy signature");
+
+ bool isValid = utils.isValidERC6492Signature(messageHash, signature, expectedSigner);
+
+ assertTrue(isValid, "Should return true for valid signature with undeployed contract");
+ }
+
+ function test_isValidERC6492Signature_returnsTrue_forValidSignature_deployed() public {
+ bytes32 salt = keccak256("test salt");
+ bool flag = true;
+
+ address expectedSigner = erc4337Factory.createAccount(flag, salt);
+ (bytes memory signature,) = getERC6492SignatureAndSigner(flag, salt, "dummy signature");
+
+ bool isValid = utils.isValidERC6492Signature(messageHash, signature, expectedSigner);
+
+ assertTrue(isValid, "Should return true for valid signature with deployed contract");
+ }
+
+ function test_isValidERC6492Signature_returnsFalse_forInvalidSignature_notDeployed() public {
+ (bytes memory signature, address expectedSigner) =
+ getERC6492SignatureAndSigner(false, keccak256("test salt"), "dummy signature");
+
+ bool isValid = utils.isValidERC6492Signature(messageHash, signature, expectedSigner);
+
+ assertFalse(isValid, "Should return false for invalid signature with undeployed contract");
+ }
+
+ function test_isValidERC6492Signature_reverts_forValidSignature_errorOnDeployment() public {
+ address expectedSigner = address(0x12345678); // Simulate a contract that will not deploy correctly
+ bytes memory originalSig = "dummy signature";
+
+ bytes memory factoryCalldata =
+ abi.encodeWithSelector(MockERC4337Factory.createAccount.selector, "corrupted data", keccak256("test salt"));
+
+ bytes memory erc6492Sig = abi.encode(address(erc4337Factory), factoryCalldata, originalSig);
+ bytes memory sigWithSuffix = abi.encodePacked(erc6492Sig, Utils.ERC6492_DETECTION_SUFFIX);
+
+ vm.expectRevert(abi.encodeWithSelector(Utils.ERC6492DeploymentFailed.selector, erc4337Factory, factoryCalldata));
+ utils.isValidERC6492Signature(messageHash, sigWithSuffix, expectedSigner);
+ }
+
+ function test_isValidERC6492Signature_reverts_forWrongExpectedSigner_thatIsNotContract() public {
+ address wrongExpectedSigner = address(0x33231234); // Wrong address
+
+ (bytes memory signature,) = getERC6492SignatureAndSigner(true, keccak256("test salt"), "dummy signature");
+
+ vm.expectRevert(abi.encodeWithSelector(Utils.ERC6492NoCode.selector, wrongExpectedSigner));
+ utils.isValidERC6492Signature(messageHash, signature, wrongExpectedSigner);
+ }
+
+ function test_isValidERC6492Signature_returnsFalse_forWrongExpectedSigner_thatIsContract() public {
+ address wrongExpectedSigner = address(new MockFlagERC1271(false)); // Different deployed contract
+
+ (bytes memory signature,) = getERC6492SignatureAndSigner(true, keccak256("test salt"), "dummy signature");
+
+ bool isValid = utils.isValidERC6492Signature(messageHash, signature, wrongExpectedSigner);
+
+ assertFalse(isValid, "Should return false when expectedSigner is a different deployed contract");
+ }
+}
+
+contract UtilsTest_StateSignatures is UtilsTest_SignaturesBase {
+ MockERC20 public token;
+ MockERC4337Factory public factory;
+
+ Channel public channel;
+ State public testState;
+
+ bytes32 public channelId;
+
+ function setUp() public override {
+ super.setUp();
+
+ token = new MockERC20("Test Token", "TEST", 18);
+ factory = new MockERC4337Factory();
+
+ // Create test channel
+ address[] memory participants = new address[](2);
+ participants[0] = signer;
+ participants[1] = wrongSigner;
+
+ channel = Channel({participants: participants, adjudicator: address(0x123), challenge: 3600, nonce: 1});
+
+ // Create test state
+ Allocation[] memory allocations = new Allocation[](2);
+ allocations[0] = Allocation({destination: signer, token: address(token), amount: 100});
+ allocations[1] = Allocation({destination: wrongSigner, token: address(token), amount: 200});
+
+ testState = State({
+ intent: StateIntent.INITIALIZE,
+ version: 0,
+ data: bytes("test data"),
+ allocations: allocations,
+ sigs: new bytes[](0)
+ });
+
+ channelId = utils.getChannelId(channel);
+ }
+
+ // ==================== recoverStateEIP712Signer TESTS ====================
+
+ function test_recoverStateEIP712Signer_returnsCorrectSigner() public view {
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ address recoveredSigner =
+ utils.recoverStateEIP712Signer(domainSeparator, STATE_TYPEHASH, channelId, testState, sig);
+
+ assertEq(recoveredSigner, signer, "Should recover correct signer for state EIP712");
+ }
+
+ function test_recoverStateEIP712Signer_returnsWrongSignerForDifferentState() public view {
+ // Sign original state
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ // Create different state
+ State memory differentState = testState;
+ differentState.data = bytes("different data");
+
+ address recoveredSigner =
+ utils.recoverStateEIP712Signer(domainSeparator, STATE_TYPEHASH, channelId, differentState, sig);
+
+ assertNotEq(recoveredSigner, signer, "Should not recover correct signer for different state");
+ }
+
+ // ==================== verifyStateEOASignature TESTS ====================
+
+ function test_verifyStateEOASignature_returnsTrue_forRawECDSASignature() public view {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify raw ECDSA signature");
+ }
+
+ function test_verifyStateEOASignature_returnsTrue_forEIP191Signature() public view {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.signEIP191(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify EIP191 signature");
+ }
+
+ function test_verifyStateEOASignature_returnsTrue_forEIP712Signature() public view {
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify EIP712 signature");
+ }
+
+ function test_verifyStateEOASignature_returnsFalse_forWrongSigner_rawECDSA() public view {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, wrongSigner);
+
+ assertFalse(isValid, "Should not verify signature for wrong signer");
+ }
+
+ function test_verifyStateEOASignature_returnsFalse_forWrongSigner_EIP191() public view {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.signEIP191(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, wrongSigner);
+
+ assertFalse(isValid, "Should not verify EIP191 signature for wrong signer");
+ }
+
+ function test_verifyStateEOASignature_returnsFalse_forWrongSigner_EIP712() public view {
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, wrongSigner);
+
+ assertFalse(isValid, "Should not verify EIP712 signature for wrong signer");
+ }
+
+ function test_verifyStateEOASignature_returnsFalse_whenNoEIP712Support() public view {
+ bytes32 domainSeparator = Utils.NO_EIP712_SUPPORT;
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertFalse(isValid, "Should not verify EIP712 signature when NO_EIP712_SUPPORT is set");
+ }
+
+ function test_verifyStateEOASignature_returnsTrue_forRawECDSAWhenNoEIP712Support() public view {
+ bytes32 domainSeparator = Utils.NO_EIP712_SUPPORT;
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify raw ECDSA signature even when NO_EIP712_SUPPORT is set");
+ }
+
+ function test_verifyStateEOASignature_returnsFalse_forEIP712WhenNoEIP712Support() public view {
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ bool isValid = utils.verifyStateEOASignature(testState, channelId, Utils.NO_EIP712_SUPPORT, sig, signer);
+
+ assertFalse(isValid, "Should not verify EIP712 signature when NO_EIP712_SUPPORT is set");
+ }
+
+ // ==================== verifyStateSignature TESTS ====================
+
+ // EOA Signature Tests
+ function test_verifyStateSignature_returnsTrue_forEOA_rawECDSASignature() public {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify raw ECDSA signature for EOA");
+ }
+
+ function test_verifyStateSignature_returnsTrue_forEOA_EIP191Signature() public {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.signEIP191(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify EIP191 signature for EOA");
+ }
+
+ function test_verifyStateSignature_returnsTrue_forEOA_EIP712Signature() public {
+ bytes32 structHash = keccak256(
+ abi.encode(
+ STATE_TYPEHASH,
+ channelId,
+ testState.intent,
+ testState.version,
+ keccak256(testState.data),
+ keccak256(abi.encode(testState.allocations))
+ )
+ );
+ bytes memory sig = TestUtils.signEIP712(vm, signerPrivateKey, domainSeparator, structHash);
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, sig, signer);
+
+ assertTrue(isValid, "Should verify EIP712 signature for EOA");
+ }
+
+ function test_verifyStateSignature_returnsFalse_forEOA_wrongSigner() public {
+ bytes memory packedState = utils.getPackedState(channelId, testState);
+ bytes memory sig = TestUtils.sign(vm, signerPrivateKey, packedState);
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, sig, wrongSigner);
+
+ assertFalse(isValid, "Should not verify signature for wrong EOA signer");
+ }
+
+ // ERC-1271 Contract Signature Tests
+ function test_verifyStateSignature_returnsTrue_forERC1271Contract_validSignature() public {
+ MockFlagERC1271 mockContract = new MockFlagERC1271(true);
+ bytes memory sig = "dummy signature";
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, sig, address(mockContract));
+
+ assertTrue(isValid, "Should verify valid ERC1271 contract signature");
+ }
+
+ function test_verifyStateSignature_returnsFalse_forERC1271Contract_invalidSignature() public {
+ MockFlagERC1271 mockContract = new MockFlagERC1271(false);
+ bytes memory sig = "dummy signature";
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, sig, address(mockContract));
+
+ assertFalse(isValid, "Should not verify invalid ERC1271 contract signature");
+ }
+
+ // ERC-6492 Signature Tests
+ function test_verifyStateSignature_returnsTrue_forERC6492_validSignature_notDeployed() public {
+ (bytes memory signature, address expectedSigner) =
+ getERC6492SignatureAndSigner(true, keccak256("test salt"), "dummy signature");
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, signature, expectedSigner);
+
+ assertTrue(isValid, "Should verify valid ERC6492 signature for undeployed contract");
+ }
+
+ function test_verifyStateSignature_returnsTrue_forERC6492_validSignature_deployed() public {
+ bytes32 salt = keccak256("test salt");
+ bool flag = true;
+
+ address expectedSigner = factory.createAccount(flag, salt);
+ (bytes memory signature,) = getERC6492SignatureAndSigner(flag, salt, "dummy signature");
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, signature, expectedSigner);
+
+ assertTrue(isValid, "Should verify valid ERC6492 signature for deployed contract");
+ }
+
+ function test_verifyStateSignature_returnsFalse_forERC6492_invalidSignature_notDeployed() public {
+ (bytes memory signature, address expectedSigner) =
+ getERC6492SignatureAndSigner(false, keccak256("test salt"), "dummy signature");
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, signature, expectedSigner);
+
+ assertFalse(isValid, "Should not verify invalid ERC6492 signature for undeployed contract");
+ }
+
+ function test_verifyStateSignature_returnsFalse_forERC6492_invalidSignature_deployed() public {
+ bytes32 salt = keccak256("test salt");
+ bool flag = false;
+
+ address expectedSigner = factory.createAccount(flag, salt);
+ (bytes memory signature,) = getERC6492SignatureAndSigner(flag, salt, "dummy signature");
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, signature, expectedSigner);
+
+ assertFalse(isValid, "Should not verify invalid ERC6492 signature for deployed contract");
+ }
+
+ function test_verifyStateSignature_returnsFalse_forERC6492_wrongExpectedSigner() public {
+ address wrongExpectedSigner = address(new MockFlagERC1271(false));
+
+ (bytes memory signature,) = getERC6492SignatureAndSigner(true, keccak256("test salt"), "dummy signature");
+
+ bool isValid = utils.verifyStateSignature(testState, channelId, domainSeparator, signature, wrongExpectedSigner);
+
+ assertFalse(isValid, "Should not verify ERC6492 signature for wrong expected signer");
+ }
+}
diff --git a/contract/test/UtilsHarness.sol b/contract/test/UtilsHarness.sol
new file mode 100644
index 000000000..615ccdb8d
--- /dev/null
+++ b/contract/test/UtilsHarness.sol
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {Utils} from "../src/Utils.sol";
+import {Channel, State, StateIntent} from "../src/interfaces/Types.sol";
+
+contract UtilsHarness {
+ using Utils for *;
+
+ function getChannelId(Channel memory ch) external view returns (bytes32) {
+ return Utils.getChannelId(ch);
+ }
+
+ function getPackedState(bytes32 channelId, State memory state) external pure returns (bytes memory) {
+ return Utils.getPackedState(channelId, state);
+ }
+
+ function recoverRawECDSASigner(bytes memory message, bytes memory sig) external pure returns (address) {
+ return Utils.recoverRawECDSASigner(message, sig);
+ }
+
+ function recoverEIP191Signer(bytes memory message, bytes memory sig) external pure returns (address) {
+ return Utils.recoverEIP191Signer(message, sig);
+ }
+
+ function recoverEIP712Signer(bytes32 domainSeparator, bytes32 structHash, bytes memory sig)
+ external
+ pure
+ returns (address)
+ {
+ return Utils.recoverEIP712Signer(domainSeparator, structHash, sig);
+ }
+
+ function recoverStateEIP712Signer(
+ bytes32 domainSeparator,
+ bytes32 typeHash,
+ bytes32 channelId,
+ State memory state,
+ bytes memory sig
+ ) external pure returns (address) {
+ return Utils.recoverStateEIP712Signer(domainSeparator, typeHash, channelId, state, sig);
+ }
+
+ function verifyStateEOASignature(
+ State memory state,
+ bytes32 channelId,
+ bytes32 domainSeparator,
+ bytes memory sig,
+ address signer
+ ) external pure returns (bool) {
+ return Utils.verifyStateEOASignature(state, channelId, domainSeparator, sig, signer);
+ }
+
+ function isValidERC1271Signature(bytes32 msgHash, bytes memory sig, address expectedSigner)
+ external
+ view
+ returns (bool)
+ {
+ return Utils.isValidERC1271Signature(msgHash, sig, expectedSigner);
+ }
+
+ function isValidERC6492Signature(bytes32 msgHash, bytes memory sig, address expectedSigner)
+ external
+ returns (bool)
+ {
+ return Utils.isValidERC6492Signature(msgHash, sig, expectedSigner);
+ }
+
+ function verifyStateSignature(
+ State memory state,
+ bytes32 channelId,
+ bytes32 domainSeparator,
+ bytes memory sig,
+ address signer
+ ) external returns (bool) {
+ return Utils.verifyStateSignature(state, channelId, domainSeparator, sig, signer);
+ }
+
+ function validateInitialState(State memory state, Channel memory chan, bytes32 domainSeparator)
+ external
+ returns (bool)
+ {
+ return Utils.validateInitialState(state, chan, domainSeparator);
+ }
+
+ function validateUnanimousStateSignatures(State memory state, Channel memory chan, bytes32 domainSeparator)
+ external
+ returns (bool)
+ {
+ return Utils.validateUnanimousStateSignatures(state, chan, domainSeparator);
+ }
+
+ function statesAreEqual(State memory a, State memory b) external pure returns (bool) {
+ return Utils.statesAreEqual(a, b);
+ }
+
+ function validateTransitionTo(State memory previous, State memory candidate) external pure returns (bool) {
+ return Utils.validateTransitionTo(previous, candidate);
+ }
+
+ function trailingBytes32(bytes memory data) external pure returns (bytes32) {
+ return Utils.trailingBytes32(data);
+ }
+}
diff --git a/contract/test/adjudicators/ConsensusTransition.t.sol b/contract/test/adjudicators/ConsensusTransition.t.sol
index 658dd88e6..8e905e555 100644
--- a/contract/test/adjudicators/ConsensusTransition.t.sol
+++ b/contract/test/adjudicators/ConsensusTransition.t.sol
@@ -10,7 +10,7 @@ import {TestUtils} from "../TestUtils.sol";
import {MockERC20} from "../mocks/MockERC20.sol";
import {IAdjudicator} from "../../src/interfaces/IAdjudicator.sol";
-import {Channel, State, Allocation, Signature, StateIntent} from "../../src/interfaces/Types.sol";
+import {Channel, State, Allocation, StateIntent} from "../../src/interfaces/Types.sol";
import {ConsensusTransition} from "../../src/adjudicators/ConsensusTransition.sol";
import {Utils} from "../../src/Utils.sol";
@@ -19,6 +19,10 @@ contract ConsensusTransitionTest is Test {
ConsensusTransition public adjudicator;
+ // Mockup constructor parameters
+ address mockedOwner = address(0x456);
+ address mockedChannelImpl = address(0x123);
+
// Test accounts
address public host;
address public guest;
@@ -35,7 +39,7 @@ contract ConsensusTransitionTest is Test {
function setUp() public {
// Deploy the adjudicator contract
- adjudicator = new ConsensusTransition();
+ adjudicator = new ConsensusTransition(mockedOwner, mockedChannelImpl);
// Generate private keys and addresses for the participants
hostPrivateKey = 0x1;
@@ -58,29 +62,6 @@ contract ConsensusTransitionTest is Test {
});
}
- // Test case: Basic signing test to verify our signature approach
- // using foundry's vm.sign and correctly formatted message for ecrecover
- function test_BasicSignatureVerification() public view {
- // Simple message to sign
- bytes32 message = keccak256("test message");
-
- // Sign the message directly without EIP-191 prefix
- (uint8 v, bytes32 r, bytes32 s) = TestUtils.sign(vm, hostPrivateKey, message);
-
- // Recover the signer using direct recovery (no prefix)
- address recovered = ECDSA.recover(message, v, r, s);
-
- // This should pass - verifying our signing approach
- assertEq(recovered, host);
-
- // Now test with Utils.verifySignature
- Signature memory signature = Signature({v: v, r: r, s: s});
- bool isValid = Utils.verifySignature(message, signature, host);
-
- // This should also pass with our updated Utils.verifySignature
- assertTrue(isValid);
- }
-
// Helper function to create test allocations
function createAllocations(uint256 hostAmount, uint256 guestAmount) internal view returns (Allocation[2] memory) {
Allocation[2] memory allocations;
@@ -105,7 +86,7 @@ contract ConsensusTransitionTest is Test {
state.allocations = new Allocation[](2);
state.allocations[HOST] = allocations[HOST];
state.allocations[GUEST] = allocations[GUEST];
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
@@ -123,7 +104,7 @@ contract ConsensusTransitionTest is Test {
state.allocations = new Allocation[](2);
state.allocations[HOST] = allocations[HOST];
state.allocations[GUEST] = allocations[GUEST];
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
@@ -145,30 +126,29 @@ contract ConsensusTransitionTest is Test {
state.allocations = new Allocation[](2);
state.allocations[HOST] = allocations[HOST];
state.allocations[GUEST] = allocations[GUEST];
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
// Helper to sign a state
- function _signState(State memory state, uint256 privateKey) internal view returns (Signature memory) {
- bytes32 stateHash = Utils.getStateHash(channel, state);
- (uint8 v, bytes32 r, bytes32 s) = TestUtils.sign(vm, privateKey, stateHash);
- return Signature({v: v, r: r, s: s});
+ function _signState(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(channel), state);
+ return TestUtils.sign(vm, privateKey, packedState);
}
// -------------------- FIRST STATE TRANSITION TESTS --------------------
- function test_adjudicate_firstState_valid() public view {
+ function test_adjudicate_firstState_valid() public {
// Create initial state with both signatures
State memory initialState = _createInitialState("initial state");
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first operate state with both signatures
State memory firstState = _createOperateState("first state", 1);
- firstState.sigs = new Signature[](2);
+ firstState.sigs = new bytes[](2);
firstState.sigs[HOST] = _signState(firstState, hostPrivateKey);
firstState.sigs[GUEST] = _signState(firstState, guestPrivateKey);
@@ -181,16 +161,16 @@ contract ConsensusTransitionTest is Test {
assertTrue(valid, "Valid first state transition should be accepted");
}
- function test_adjudicate_firstState_revert_whenMissingParticipantSignature() public view {
+ function test_adjudicate_firstState_revert_whenMissingParticipantSignature() public {
// Create initial state with both signatures
State memory initialState = _createInitialState("initial state");
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first operate state with only one signature
State memory firstState = _createOperateState("first state", 1);
- firstState.sigs = new Signature[](1);
+ firstState.sigs = new bytes[](1);
firstState.sigs[0] = _signState(firstState, hostPrivateKey);
// Provide the initial state as proof
@@ -202,16 +182,16 @@ contract ConsensusTransitionTest is Test {
assertFalse(valid, "First state without both signatures should be rejected");
}
- function test_adjudicate_firstState_revert_whenIncorrectVersion() public view {
+ function test_adjudicate_firstState_revert_whenIncorrectVersion() public {
// Create initial state with both signatures
State memory initialState = _createInitialState("initial state");
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first operate state with incorrect version (2 instead of 1)
State memory firstState = _createOperateState("first state", 2);
- firstState.sigs = new Signature[](2);
+ firstState.sigs = new bytes[](2);
firstState.sigs[HOST] = _signState(firstState, hostPrivateKey);
firstState.sigs[GUEST] = _signState(firstState, guestPrivateKey);
@@ -226,16 +206,16 @@ contract ConsensusTransitionTest is Test {
// -------------------- LATER STATE TRANSITION TESTS --------------------
- function test_adjudicate_laterState_valid() public view {
+ function test_adjudicate_laterState_valid() public {
// Create state 1 with both signatures
State memory state1 = _createOperateState("state 1", 1);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
// Create state 2 with both signatures
State memory state2 = _createOperateState("state 2", 2);
- state2.sigs = new Signature[](2);
+ state2.sigs = new bytes[](2);
state2.sigs[HOST] = _signState(state2, hostPrivateKey);
state2.sigs[GUEST] = _signState(state2, guestPrivateKey);
@@ -248,16 +228,16 @@ contract ConsensusTransitionTest is Test {
assertTrue(valid, "Valid state transition from 1 to 2 should be accepted");
}
- function test_adjudicate_laterState_revert_whenIncorrectVersionIncrement() public view {
+ function test_adjudicate_laterState_revert_whenIncorrectVersionIncrement() public {
// Create state 1 with both signatures
State memory state1 = _createOperateState("state 1", 1);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
// Create state 3 with both signatures (skipping version 2)
State memory state3 = _createOperateState("state 3", 3);
- state3.sigs = new Signature[](2);
+ state3.sigs = new bytes[](2);
state3.sigs[HOST] = _signState(state3, hostPrivateKey);
state3.sigs[GUEST] = _signState(state3, guestPrivateKey);
@@ -270,10 +250,10 @@ contract ConsensusTransitionTest is Test {
assertFalse(valid, "State with non-sequential version should be rejected");
}
- function test_adjudicate_laterState_revert_whenAllocationSumChanged() public view {
+ function test_adjudicate_laterState_revert_whenAllocationSumChanged() public {
// Create state 1 with both signatures
State memory state1 = _createOperateState("state 1", 1);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
@@ -281,7 +261,7 @@ contract ConsensusTransitionTest is Test {
State memory state2 = _createOperateState("state 2", 2);
state2.allocations[HOST].amount = 60; // Changed from 50
state2.allocations[GUEST].amount = 50; // Kept at 50, total sum is now 110 instead of 100
- state2.sigs = new Signature[](2);
+ state2.sigs = new bytes[](2);
state2.sigs[HOST] = _signState(state2, hostPrivateKey);
state2.sigs[GUEST] = _signState(state2, guestPrivateKey);
@@ -294,10 +274,10 @@ contract ConsensusTransitionTest is Test {
assertFalse(valid, "State with changed allocation sum should be rejected");
}
- function test_adjudicate_revert_whenNoStateProof() public view {
+ function test_adjudicate_revert_whenNoStateProof() public {
// Create state 2 without providing a proof
State memory state2 = _createOperateState("state 2", 2);
- state2.sigs = new Signature[](2);
+ state2.sigs = new bytes[](2);
state2.sigs[HOST] = _signState(state2, hostPrivateKey);
state2.sigs[GUEST] = _signState(state2, guestPrivateKey);
@@ -309,16 +289,16 @@ contract ConsensusTransitionTest is Test {
assertFalse(valid, "State without proof should be rejected");
}
- function test_adjudicate_revert_whenTooManyProofs() public view {
+ function test_adjudicate_revert_whenTooManyProofs() public {
// Create state 1
State memory state1 = _createOperateState("state 1", 1);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
// Create state 2
State memory state2 = _createOperateState("state 2", 2);
- state2.sigs = new Signature[](2);
+ state2.sigs = new bytes[](2);
state2.sigs[HOST] = _signState(state2, hostPrivateKey);
state2.sigs[GUEST] = _signState(state2, guestPrivateKey);
@@ -334,10 +314,10 @@ contract ConsensusTransitionTest is Test {
// -------------------- RESIZE STATE TRANSITION TESTS --------------------
- function test_adjudicate_afterResize_valid() public view {
+ function test_adjudicate_afterResize_valid() public {
// Create state 1 with both signatures
State memory state1 = _createOperateState("state 1", 1);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
@@ -346,13 +326,13 @@ contract ConsensusTransitionTest is Test {
resizeAmounts[HOST] = 10;
resizeAmounts[GUEST] = -10;
State memory resizeState = _createResizeState("resize state", 2, resizeAmounts);
- resizeState.sigs = new Signature[](2);
+ resizeState.sigs = new bytes[](2);
resizeState.sigs[HOST] = _signState(resizeState, hostPrivateKey);
resizeState.sigs[GUEST] = _signState(resizeState, guestPrivateKey);
// Create state 3 after resize
State memory state3 = _createOperateState("state 3", 3);
- state3.sigs = new Signature[](2);
+ state3.sigs = new bytes[](2);
state3.sigs[HOST] = _signState(state3, hostPrivateKey);
state3.sigs[GUEST] = _signState(state3, guestPrivateKey);
@@ -366,10 +346,10 @@ contract ConsensusTransitionTest is Test {
}
// Test signature validation using a non-corrupt signature but wrong signer
- function test_WrongSignerRejected() public view {
+ function test_WrongSignerRejected() public {
// Create state with signatures from wrong participants
State memory state = _createOperateState("state", 1);
- state.sigs = new Signature[](2);
+ state.sigs = new bytes[](2);
// Use guest's signature for both slots
state.sigs[HOST] = _signState(state, guestPrivateKey); // Should be host, but using guest
@@ -377,7 +357,7 @@ contract ConsensusTransitionTest is Test {
// Create a valid initial state as proof
State memory initialState = _createInitialState("initial state");
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
diff --git a/contract/test/adjudicators/Counter.t.sol b/contract/test/adjudicators/Counter.t.sol
index 3b4b369d6..ffc0bfb34 100644
--- a/contract/test/adjudicators/Counter.t.sol
+++ b/contract/test/adjudicators/Counter.t.sol
@@ -10,7 +10,7 @@ import {ECDSA} from "lib/openzeppelin-contracts/contracts/utils/cryptography/ECD
import {TestUtils} from "../TestUtils.sol";
import {IAdjudicator} from "../../src/interfaces/IAdjudicator.sol";
-import {Channel, State, Allocation, Signature, StateIntent} from "../../src/interfaces/Types.sol";
+import {Channel, State, Allocation, StateIntent} from "../../src/interfaces/Types.sol";
import {Counter, Counter as CounterContract} from "../../src/adjudicators/Counter.sol";
import {Utils} from "../../src/Utils.sol";
@@ -65,7 +65,7 @@ contract CounterTest is Test {
state.allocations[HOST] = Allocation({destination: address(0), token: address(0), amount: 100});
state.allocations[GUEST] = Allocation({destination: address(0), token: address(0), amount: 100});
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
@@ -82,7 +82,7 @@ contract CounterTest is Test {
state.allocations[HOST] = Allocation({destination: address(0), token: address(0), amount: 100});
state.allocations[GUEST] = Allocation({destination: address(0), token: address(0), amount: 100});
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
@@ -103,28 +103,27 @@ contract CounterTest is Test {
state.allocations[HOST] = Allocation({destination: address(0), token: address(0), amount: 100});
state.allocations[GUEST] = Allocation({destination: address(0), token: address(0), amount: 100});
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
- function _signState(State memory state, uint256 privateKey) internal view returns (Signature memory) {
- bytes32 stateHash = Utils.getStateHash(channel, state);
- (uint8 v, bytes32 r, bytes32 s) = TestUtils.sign(vm, privateKey, stateHash);
- return Signature({v: v, r: r, s: s});
+ function _signState(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(channel), state);
+ return TestUtils.sign(vm, privateKey, packedState);
}
// -------------------- FIRST STATE TRANSITION TESTS --------------------
- function test_adjudicate_firstState_valid() public view {
+ function test_adjudicate_firstState_valid() public {
// Create initial state with target 10
State memory initialState = _createInitialState(10);
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first state (version 1)
State memory firstState = _createCounterState(10, 1);
- firstState.sigs = new Signature[](1);
+ firstState.sigs = new bytes[](1);
firstState.sigs[0] = _signState(firstState, hostPrivateKey); // Host signs state 1
// Provide the initial state as proof
@@ -136,16 +135,16 @@ contract CounterTest is Test {
assertTrue(valid, "Valid first state transition should be accepted");
}
- function test_adjudicate_firstState_revert_whenTargetExceeded() public view {
+ function test_adjudicate_firstState_revert_whenTargetExceeded() public {
// Create initial state with target 10
State memory initialState = _createInitialState(10);
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first state with version exceeding target
State memory firstState = _createCounterState(10, 11); // version > target
- firstState.sigs = new Signature[](1);
+ firstState.sigs = new bytes[](1);
firstState.sigs[0] = _signState(firstState, hostPrivateKey);
// Provide the initial state as proof
@@ -157,16 +156,16 @@ contract CounterTest is Test {
assertFalse(valid, "State with version exceeding target should be rejected");
}
- function test_adjudicate_firstState_revert_whenIncorrectSigner() public view {
+ function test_adjudicate_firstState_revert_whenIncorrectSigner() public {
// Create initial state with target 10
State memory initialState = _createInitialState(10);
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first state signed by guest (should be host)
State memory firstState = _createCounterState(10, 1);
- firstState.sigs = new Signature[](1);
+ firstState.sigs = new bytes[](1);
firstState.sigs[0] = _signState(firstState, guestPrivateKey); // Guest signs instead of host
// Provide the initial state as proof
@@ -178,17 +177,17 @@ contract CounterTest is Test {
assertFalse(valid, "First state signed by incorrect participant should be rejected");
}
- function test_adjudicate_firstState_revert_whenWrongIntent() public view {
+ function test_adjudicate_firstState_revert_whenWrongIntent() public {
// Create initial state with target 10
State memory initialState = _createInitialState(10);
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
// Create first state with incorrect intent
State memory firstState = _createCounterState(10, 1);
firstState.intent = StateIntent.FINALIZE; // Wrong intent for first operational state
- firstState.sigs = new Signature[](1);
+ firstState.sigs = new bytes[](1);
firstState.sigs[0] = _signState(firstState, hostPrivateKey);
// Provide the initial state as proof
@@ -202,15 +201,15 @@ contract CounterTest is Test {
// -------------------- LATER STATE TRANSITION TESTS --------------------
- function test_adjudicate_laterState_valid() public view {
+ function test_adjudicate_laterState_valid() public {
// Create state 1
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create state 2 (signed by guest)
State memory state2 = _createCounterState(10, 2);
- state2.sigs = new Signature[](1);
+ state2.sigs = new bytes[](1);
state2.sigs[0] = _signState(state2, guestPrivateKey);
// Provide state 1 as proof for state 2
@@ -222,15 +221,15 @@ contract CounterTest is Test {
assertTrue(valid, "Valid state transition from 1 to 2 should be accepted");
}
- function test_adjudicate_laterState_revert_whenIncorrectVersionIncrement() public view {
+ function test_adjudicate_laterState_revert_whenIncorrectVersionIncrement() public {
// Create state 1
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create state 3 (skipping version 2)
State memory state3 = _createCounterState(10, 3);
- state3.sigs = new Signature[](1);
+ state3.sigs = new bytes[](1);
state3.sigs[0] = _signState(state3, guestPrivateKey);
// Provide state 1 as proof for state 3
@@ -242,15 +241,15 @@ contract CounterTest is Test {
assertFalse(valid, "State with non-sequential version should be rejected");
}
- function test_adjudicate_laterState_revert_whenTargetChanged() public view {
+ function test_adjudicate_laterState_revert_whenTargetChanged() public {
// Create state 1 with target 10
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create state 2 with different target
State memory state2 = _createCounterState(15, 2);
- state2.sigs = new Signature[](1);
+ state2.sigs = new bytes[](1);
state2.sigs[0] = _signState(state2, guestPrivateKey);
// Provide state 1 as proof for state 2
@@ -262,17 +261,17 @@ contract CounterTest is Test {
assertFalse(valid, "State with changed target should be rejected");
}
- function test_adjudicate_laterState_revert_whenAllocationSumChanged() public view {
+ function test_adjudicate_laterState_revert_whenAllocationSumChanged() public {
// Create state 1
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create state 2 with different allocation sums
State memory state2 = _createCounterState(10, 2);
state2.allocations[HOST].amount = 120; // Changed from 100
state2.allocations[GUEST].amount = 100;
- state2.sigs = new Signature[](1);
+ state2.sigs = new bytes[](1);
state2.sigs[0] = _signState(state2, guestPrivateKey);
// Provide state 1 as proof for state 2
@@ -284,15 +283,15 @@ contract CounterTest is Test {
assertFalse(valid, "State with changed allocation sum should be rejected");
}
- function test_adjudicate_laterState_revert_whenWrongSigner() public view {
+ function test_adjudicate_laterState_revert_whenWrongSigner() public {
// Create state 1
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create state 2 signed by host (should be guest)
State memory state2 = _createCounterState(10, 2);
- state2.sigs = new Signature[](1);
+ state2.sigs = new bytes[](1);
state2.sigs[0] = _signState(state2, hostPrivateKey); // Host signs again instead of guest
// Provide state 1 as proof for state 2
@@ -304,10 +303,10 @@ contract CounterTest is Test {
assertFalse(valid, "State signed by incorrect participant should be rejected");
}
- function test_adjudicate_revert_whenNoStateProof() public view {
+ function test_adjudicate_revert_whenNoStateProof() public {
// Create state 2 without providing a proof
State memory state2 = _createCounterState(10, 2);
- state2.sigs = new Signature[](1);
+ state2.sigs = new bytes[](1);
state2.sigs[0] = _signState(state2, guestPrivateKey);
// Provide empty proofs array
@@ -318,15 +317,15 @@ contract CounterTest is Test {
assertFalse(valid, "Non-initial state without proof should be rejected");
}
- function test_adjudicate_revert_whenTooManyProofs() public view {
+ function test_adjudicate_revert_whenTooManyProofs() public {
// Create state 1
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create state 2
State memory state2 = _createCounterState(10, 2);
- state2.sigs = new Signature[](1);
+ state2.sigs = new bytes[](1);
state2.sigs[0] = _signState(state2, guestPrivateKey);
// Provide both state 0 and state 1 as proofs (too many)
@@ -341,10 +340,10 @@ contract CounterTest is Test {
// -------------------- RESIZE STATE TRANSITION TESTS --------------------
- function test_adjudicate_afterResize_valid() public view {
+ function test_adjudicate_afterResize_valid() public {
// Create state 1
State memory state1 = _createCounterState(10, 1);
- state1.sigs = new Signature[](1);
+ state1.sigs = new bytes[](1);
state1.sigs[0] = _signState(state1, hostPrivateKey);
// Create resize state 2
@@ -352,12 +351,12 @@ contract CounterTest is Test {
resizeAmounts[0] = 20;
resizeAmounts[1] = -20;
State memory resizeState = _createResizeState(10, 2, resizeAmounts);
- resizeState.sigs = new Signature[](1);
+ resizeState.sigs = new bytes[](1);
resizeState.sigs[0] = _signState(resizeState, guestPrivateKey);
// Create state 3 after resize (valid operation state)
State memory state3 = _createCounterState(10, 3);
- state3.sigs = new Signature[](1);
+ state3.sigs = new bytes[](1);
state3.sigs[0] = _signState(state3, hostPrivateKey);
// Provide resize state as proof for state 3
@@ -369,18 +368,18 @@ contract CounterTest is Test {
assertTrue(valid, "Valid state transition after resize should be accepted");
}
- function test_adjudicate_afterResize_revert_whenTargetChanged() public view {
+ function test_adjudicate_afterResize_revert_whenTargetChanged() public {
// Create resize state 2
int256[] memory resizeAmounts = new int256[](2);
resizeAmounts[0] = 20;
resizeAmounts[1] = -20;
State memory resizeState = _createResizeState(10, 2, resizeAmounts);
- resizeState.sigs = new Signature[](1);
+ resizeState.sigs = new bytes[](1);
resizeState.sigs[0] = _signState(resizeState, guestPrivateKey);
// Create state 3 after resize with different target
State memory state3 = _createCounterState(15, 3); // Changed target from 10 to 15
- state3.sigs = new Signature[](1);
+ state3.sigs = new bytes[](1);
state3.sigs[0] = _signState(state3, hostPrivateKey);
// Provide resize state as proof for state 3
diff --git a/contract/test/adjudicators/EIP712AdjudicatorBaseTest.t.sol b/contract/test/adjudicators/EIP712AdjudicatorBaseTest.t.sol
new file mode 100644
index 000000000..a77ebb3ef
--- /dev/null
+++ b/contract/test/adjudicators/EIP712AdjudicatorBaseTest.t.sol
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {Test} from "lib/forge-std/src/Test.sol";
+import {IERC5267} from "lib/openzeppelin-contracts/contracts/interfaces/IERC5267.sol";
+import {Ownable} from "lib/openzeppelin-contracts/contracts/access/Ownable.sol";
+
+import {EIP712AdjudicatorBase} from "../../src/adjudicators/EIP712AdjudicatorBase.sol";
+import {TestEIP712Adjudicator} from "../mocks/TestEIP712Adjudicator.sol";
+import {MockERC20} from "../mocks/MockERC20.sol";
+import {MockEIP712} from "../mocks/MockEIP712.sol";
+import {Utils} from "../../src/Utils.sol";
+
+contract EIP712AdjudicatorBaseTest is Test {
+ TestEIP712Adjudicator public adjudicator;
+ MockERC20 public token;
+
+ address public owner = address(0x1);
+ address public notOwner = address(0x2);
+ address public channelImpl = address(0x3);
+ address public nonContractAddress = address(0x4);
+ address public newChannelImpl = address(0x5);
+
+ function setUp() public {
+ token = new MockERC20("Test Token", "TEST", 18);
+ adjudicator = new TestEIP712Adjudicator(owner, channelImpl);
+ }
+
+ function test_constructor_setsValues() public view {
+ assertEq(adjudicator.owner(), owner, "Owner should be set correctly");
+ assertEq(address(adjudicator.channelImpl()), channelImpl, "Channel implementation should be set correctly");
+ }
+
+ function test_getChannelImplDomainSeparator_returnsCorrectCustodyDomainSeparator() public {
+ // Deploy a contract that implements EIP712
+ MockEIP712 mockCustody = new MockEIP712("Custody", "1.0");
+ TestEIP712Adjudicator custodyAdjudicator = new TestEIP712Adjudicator(owner, address(mockCustody));
+
+ bytes32 result = custodyAdjudicator.getChannelImplDomainSeparator();
+ bytes32 expectedDomainSeparator = mockCustody.domainSeparator();
+
+ assertEq(result, expectedDomainSeparator, "Domain separator should match expected value");
+ }
+
+ function test_getChannelImplDomainSeparator_returnsNoEip712Support_whenContractDoesNotSupportEip712() public {
+ // Set MockERC20 as channel impl (doesn't support EIP712)
+ vm.prank(owner);
+ adjudicator.setChannelImpl(address(token));
+
+ bytes32 result = adjudicator.getChannelImplDomainSeparator();
+
+ assertEq(result, Utils.NO_EIP712_SUPPORT, "Should return NO_EIP712_SUPPORT for non-EIP712 contract");
+ }
+
+ function test_getChannelImplDomainSeparator_returnsNoEip712Support_whenAddressIsNotContract() public {
+ // Set non-contract address as channel impl
+ vm.prank(owner);
+ adjudicator.setChannelImpl(nonContractAddress);
+
+ bytes32 result = adjudicator.getChannelImplDomainSeparator();
+
+ assertEq(result, Utils.NO_EIP712_SUPPORT, "Should return NO_EIP712_SUPPORT for non-contract address");
+ }
+
+ function test_setChannelImpl_onlyWorksForOwner() public {
+ vm.prank(owner);
+ adjudicator.setChannelImpl(newChannelImpl);
+
+ assertEq(address(adjudicator.channelImpl()), newChannelImpl, "Channel impl should be updated by owner");
+ }
+
+ function test_setChannelImpl_failsForNotOwner() public {
+ vm.prank(notOwner);
+ vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, notOwner));
+ adjudicator.setChannelImpl(newChannelImpl);
+ }
+}
diff --git a/contract/test/adjudicators/SimpleConsensus.t.sol b/contract/test/adjudicators/SimpleConsensus.t.sol
index 32e9ac8b8..de48c6368 100644
--- a/contract/test/adjudicators/SimpleConsensus.t.sol
+++ b/contract/test/adjudicators/SimpleConsensus.t.sol
@@ -8,9 +8,10 @@ import {ECDSA} from "lib/openzeppelin-contracts/contracts/utils/cryptography/ECD
import {TestUtils} from "../TestUtils.sol";
import {MockERC20} from "../mocks/MockERC20.sol";
+import {MockEIP712} from "../mocks/MockEIP712.sol";
import {IAdjudicator} from "../../src/interfaces/IAdjudicator.sol";
-import {Channel, State, Allocation, Signature, StateIntent} from "../../src/interfaces/Types.sol";
+import {Channel, State, Allocation, StateIntent, STATE_TYPEHASH} from "../../src/interfaces/Types.sol";
import {SimpleConsensus} from "../../src/adjudicators/SimpleConsensus.sol";
import {Utils} from "../../src/Utils.sol";
@@ -18,6 +19,10 @@ contract SimpleConsensusTest is Test {
using ECDSA for bytes32;
SimpleConsensus public adjudicator;
+ MockEIP712 public mockedChannelImpl;
+
+ // Mockup constructor parameters
+ address mockedOwner = address(0x456);
address public host;
address public guest;
@@ -31,7 +36,8 @@ contract SimpleConsensusTest is Test {
uint256 private constant GUEST = 1;
function setUp() public {
- adjudicator = new SimpleConsensus();
+ mockedChannelImpl = new MockEIP712("TestChannelImpl", "1.0");
+ adjudicator = new SimpleConsensus(mockedOwner, address(mockedChannelImpl));
hostPrivateKey = 0x1;
guestPrivateKey = 0x2;
@@ -82,7 +88,7 @@ contract SimpleConsensusTest is Test {
state.allocations = new Allocation[](2);
state.allocations[HOST] = allocations[HOST];
state.allocations[GUEST] = allocations[GUEST];
- state.sigs = new Signature[](0);
+ state.sigs = new bytes[](0);
return state;
}
@@ -98,15 +104,25 @@ contract SimpleConsensusTest is Test {
return state;
}
- function _signState(State memory state, uint256 privateKey) internal view returns (Signature memory) {
- bytes32 stateHash = Utils.getStateHash(channel, state);
- (uint8 v, bytes32 r, bytes32 s) = TestUtils.sign(vm, privateKey, stateHash);
- return Signature({v: v, r: r, s: s});
+ function _signState(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(channel), state);
+ return TestUtils.sign(vm, privateKey, packedState);
+ }
+
+ function _signStateEIP191(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes memory packedState = Utils.getPackedState(Utils.getChannelId(channel), state);
+ return TestUtils.signEIP191(vm, privateKey, packedState);
+ }
+
+ function _signStateEIP712(State memory state, uint256 privateKey) internal view returns (bytes memory) {
+ bytes32 channelId = Utils.getChannelId(channel);
+ bytes32 domainSeparator = mockedChannelImpl.domainSeparator();
+ return TestUtils.signStateEIP712(vm, channelId, state, STATE_TYPEHASH, domainSeparator, privateKey);
}
- function test_adjudicate_firstState_valid() public view {
+ function test_adjudicate_firstState_valid_withRawECDSASignatures() public {
State memory initialState = _createInitialState("initial state");
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
@@ -114,19 +130,39 @@ contract SimpleConsensusTest is Test {
assertTrue(valid, "Valid first state transition should be accepted");
}
- function test_adjudicate_firstState_revert_whenMissingParticipantSignature() public view {
+ function test_adjudicate_firstState_valid_withEIP191Signatures() public {
+ State memory initialState = _createInitialState("initial state");
+ initialState.sigs = new bytes[](2);
+ initialState.sigs[HOST] = _signStateEIP191(initialState, hostPrivateKey);
+ initialState.sigs[GUEST] = _signStateEIP191(initialState, guestPrivateKey);
+
+ bool valid = adjudicator.adjudicate(channel, initialState, new State[](0));
+ assertTrue(valid, "Valid first state transition with EIP191 signatures should be accepted");
+ }
+
+ function test_adjudicate_firstState_valid_withEIP712Signatures() public {
+ State memory initialState = _createInitialState("initial state");
+ initialState.sigs = new bytes[](2);
+ initialState.sigs[HOST] = _signStateEIP712(initialState, hostPrivateKey);
+ initialState.sigs[GUEST] = _signStateEIP712(initialState, guestPrivateKey);
+
+ bool valid = adjudicator.adjudicate(channel, initialState, new State[](0));
+ assertTrue(valid, "Valid first state transition with EIP712 signatures should be accepted");
+ }
+
+ function test_adjudicate_firstState_revert_whenMissingParticipantSignature() public {
State memory initialState = _createInitialState("initial state");
- initialState.sigs = new Signature[](1);
+ initialState.sigs = new bytes[](1);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
bool valid = adjudicator.adjudicate(channel, initialState, new State[](0));
assertFalse(valid, "First state without both signatures should be rejected");
}
- function test_adjudicate_firstState_revert_whenIncorrectIntent() public view {
+ function test_adjudicate_firstState_revert_whenIncorrectIntent() public {
State memory initialState = _createInitialState("initial state");
initialState.intent = StateIntent.OPERATE; // Incorrect intent, should be INITIALIZE
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
@@ -134,10 +170,10 @@ contract SimpleConsensusTest is Test {
assertFalse(valid, "First state with incorrect intent should be rejected");
}
- function test_adjudicate_firstState_revert_whenIncorrectVersion() public view {
+ function test_adjudicate_firstState_revert_whenIncorrectVersion() public {
State memory initialState = _createInitialState("initial state");
initialState.version = 1; // Incorrect version, should be 0
- initialState.sigs = new Signature[](2);
+ initialState.sigs = new bytes[](2);
initialState.sigs[HOST] = _signState(initialState, hostPrivateKey);
initialState.sigs[GUEST] = _signState(initialState, guestPrivateKey);
@@ -145,9 +181,9 @@ contract SimpleConsensusTest is Test {
assertFalse(valid, "First state with incorrect version should be rejected");
}
- function test_adjudicate_laterState_valid() public view {
+ function test_adjudicate_laterState_valid() public {
State memory state1 = _createOperateState("state 42", 42);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
@@ -155,14 +191,14 @@ contract SimpleConsensusTest is Test {
assertTrue(valid, "Valid state transition from 1 to 2 should be accepted");
}
- function test_adjudicate_revert_whenTooManyProofs() public view {
+ function test_adjudicate_revert_whenTooManyProofs() public {
State memory state1 = _createOperateState("state 1", 1);
- state1.sigs = new Signature[](2);
+ state1.sigs = new bytes[](2);
state1.sigs[HOST] = _signState(state1, hostPrivateKey);
state1.sigs[GUEST] = _signState(state1, guestPrivateKey);
State memory state2 = _createOperateState("state 2", 2);
- state2.sigs = new Signature[](2);
+ state2.sigs = new bytes[](2);
state2.sigs[HOST] = _signState(state2, hostPrivateKey);
state2.sigs[GUEST] = _signState(state2, guestPrivateKey);
@@ -174,10 +210,10 @@ contract SimpleConsensusTest is Test {
}
// Test signature validation using a non-corrupt signature but wrong signer
- function test_adjudicate_revert_wrongSigner() public view {
+ function test_adjudicate_revert_wrongSigner() public {
// Create state with signatures from wrong participants
State memory state = _createOperateState("state 13", 13);
- state.sigs = new Signature[](2);
+ state.sigs = new bytes[](2);
state.sigs[HOST] = _signState(state, guestPrivateKey); // Should be host, but using guest
state.sigs[GUEST] = _signState(state, guestPrivateKey);
diff --git a/contract/test/mocks/MockEIP712.sol b/contract/test/mocks/MockEIP712.sol
new file mode 100644
index 000000000..7a573de49
--- /dev/null
+++ b/contract/test/mocks/MockEIP712.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {EIP712} from "lib/openzeppelin-contracts/contracts/utils/cryptography/EIP712.sol";
+
+contract MockEIP712 is EIP712 {
+ constructor(string memory name, string memory version) EIP712(name, version) {}
+
+ function domainSeparator() external view returns (bytes32) {
+ return _domainSeparatorV4();
+ }
+}
diff --git a/contract/test/mocks/MockERC4337Factory.sol b/contract/test/mocks/MockERC4337Factory.sol
new file mode 100644
index 000000000..d3077d46b
--- /dev/null
+++ b/contract/test/mocks/MockERC4337Factory.sol
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {MockFlagERC1271} from "./MockFlagERC1271.sol";
+
+contract MockERC4337Factory {
+ event AccountCreated(address indexed account, bytes32 salt, bool flag);
+
+ function createAccount(bool flag, bytes32 salt) external returns (address) {
+ bytes memory bytecode = abi.encodePacked(type(MockFlagERC1271).creationCode, abi.encode(flag));
+
+ address account;
+ assembly {
+ account := create2(0, add(bytecode, 0x20), mload(bytecode), salt)
+ }
+
+ require(account != address(0), "Account creation failed");
+
+ emit AccountCreated(account, salt, flag);
+ return account;
+ }
+
+ function getAddress(bool flag, bytes32 salt) external view returns (address) {
+ bytes memory bytecode = abi.encodePacked(type(MockFlagERC1271).creationCode, abi.encode(flag));
+
+ bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(bytecode)));
+
+ return address(uint160(uint256(hash)));
+ }
+}
diff --git a/contract/test/mocks/MockFlagERC1271.sol b/contract/test/mocks/MockFlagERC1271.sol
new file mode 100644
index 000000000..4897f2875
--- /dev/null
+++ b/contract/test/mocks/MockFlagERC1271.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.13;
+
+import {IERC1271} from "lib/openzeppelin-contracts/contracts/interfaces/IERC1271.sol";
+
+contract MockFlagERC1271 is IERC1271 {
+ bool private _flag;
+ bytes4 private constant ERC1271_SUCCESS = 0x1626ba7e;
+ bytes4 private constant ERC1271_FAILURE = 0xffffffff;
+
+ constructor(bool flag) {
+ _flag = flag;
+ }
+
+ function isValidSignature(bytes32, bytes memory) external view override returns (bytes4) {
+ return _flag ? ERC1271_SUCCESS : ERC1271_FAILURE;
+ }
+
+ function setFlag(bool flag) external {
+ _flag = flag;
+ }
+
+ function getFlag() external view returns (bool) {
+ return _flag;
+ }
+}
diff --git a/contract/test/mocks/TestEIP712Adjudicator.sol b/contract/test/mocks/TestEIP712Adjudicator.sol
new file mode 100644
index 000000000..c014a55d4
--- /dev/null
+++ b/contract/test/mocks/TestEIP712Adjudicator.sol
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.26;
+
+import {EIP712AdjudicatorBase} from "../../src/adjudicators/EIP712AdjudicatorBase.sol";
+
+/**
+ * @title Test EIP712 Adjudicator
+ * @notice Test contract that inherits from EIP712AdjudicatorBase for testing purposes.
+ */
+contract TestEIP712Adjudicator is EIP712AdjudicatorBase {
+ constructor(address owner, address channelImpl_) EIP712AdjudicatorBase(owner, channelImpl_) {}
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 8422f09e5..7af3512d2 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,7 +21,7 @@ services:
container_name: anvil
ports:
- "8545:8545"
- entrypoint: ["anvil"]
+ entrypoint: ["anvil"]
command: ["--host","0.0.0.0","--chain-id","31337","--accounts","15","--balance","30000"]
healthcheck:
test: ["CMD", "cast", "block-number", "--rpc-url", "http://localhost:8545"]
@@ -88,16 +88,16 @@ services:
tstamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
);
\" &&
-
+
echo 'Running pending migrations...' &&
for migration in /migrations/*.sql; do
filename=\$$(basename \$$migration) &&
version=\$$(echo \$$filename | grep -o '^[0-9]\\+') &&
-
+
if ! psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -tAc \"SELECT 1 FROM goose_db_version WHERE version_id = \$$version\" | grep -q 1; then
echo \"Applying migration: \$$filename\" &&
# Extract and execute only the Up migration
- sed -n '/^-- +goose Up/,/^-- +goose Down/p' \$$migration |
+ sed -n '/^-- +goose Up/,/^-- +goose Down/p' \$$migration |
grep -v '^-- +goose' |
psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} &&
psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -c \"INSERT INTO goose_db_version (version_id) VALUES (\$$version);\" &&
@@ -110,11 +110,11 @@ services:
echo 'Checking database...' &&
# Check if token already exists
TOKEN_EXISTS=$$(psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -t -c \"SELECT COUNT(*) FROM assets WHERE token = '$$TOKEN_ADDRESS' AND chain_id = 31337;\" | xargs) &&
-
+
if [ \"$$TOKEN_EXISTS\" -eq \"0\" ]; then
echo 'Seeding database with tokens...' &&
psql -h database -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-postgres} -c \"
- INSERT INTO assets (token, chain_id, symbol, decimals)
+ INSERT INTO assets (token, chain_id, symbol, decimals)
VALUES ('$$TOKEN_ADDRESS', 31337, 'USDC', 6);
\" &&
echo 'Database seeded with tokens successfully'
@@ -149,9 +149,9 @@ services:
ANVIL_BALANCE_CHECKER_ADDRESS: ${ANVIL_BALANCE_CHECKER_ADDRESS:-0x730dB3A1D3Ca47e7BaEb260c24C74ED4378726Bc}
ANVIL_INFURA_URL: ws://anvil:8545
CLEARNODE_DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@database:5432/${POSTGRES_DB:-postgres}?sslmode=disable
+ CLEARNODE_LOG_LEVEL: ${CLEARNODE_LOG_LEVEL:-info}
restart: unless-stopped
volumes:
postgres_data:
driver: local
-
diff --git a/erc7824-docs/.firebaserc b/erc7824-docs/.firebaserc
new file mode 100644
index 000000000..40c8735e8
--- /dev/null
+++ b/erc7824-docs/.firebaserc
@@ -0,0 +1,5 @@
+{
+ "projects": {
+ "default": "erc7824"
+ }
+}
diff --git a/erc7824-docs/.gitignore b/erc7824-docs/.gitignore
new file mode 100644
index 000000000..d3e664850
--- /dev/null
+++ b/erc7824-docs/.gitignore
@@ -0,0 +1,23 @@
+# Dependencies
+/node_modules
+
+# Production
+/build
+
+# Generated files
+.docusaurus
+.cache-loader
+
+# Misc
+.firebase
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+sdk
+frontend
\ No newline at end of file
diff --git a/erc7824-docs/README.md b/erc7824-docs/README.md
new file mode 100644
index 000000000..1a5805b96
--- /dev/null
+++ b/erc7824-docs/README.md
@@ -0,0 +1,42 @@
+# Website
+
+This repository hosts the source files from which the [erc7284.org website](https://erc7284.org) is built
+using [Docusaurus](https://docusaurus.io/), a modern static website generator.
+
+### Installation
+
+```
+$ yarn
+```
+
+### Local Development
+
+```
+$ yarn start
+```
+
+This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
+
+### Build
+
+```
+$ yarn build
+```
+
+This command generates static content into the `build` directory and can be served using any static contents hosting service.
+
+### Deployment
+
+Using SSH:
+
+```
+$ USE_SSH=true yarn deploy
+```
+
+Not using SSH:
+
+```
+$ GIT_USER= yarn deploy
+```
+
+If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
diff --git a/erc7824-docs/blog/authors.yml b/erc7824-docs/blog/authors.yml
new file mode 100644
index 000000000..8bfa5c7c4
--- /dev/null
+++ b/erc7824-docs/blog/authors.yml
@@ -0,0 +1,23 @@
+yangshun:
+ name: Yangshun Tay
+ title: Front End Engineer @ Facebook
+ url: https://github.com/yangshun
+ image_url: https://github.com/yangshun.png
+ page: true
+ socials:
+ x: yangshunz
+ github: yangshun
+
+slorber:
+ name: Sébastien Lorber
+ title: Docusaurus maintainer
+ url: https://sebastienlorber.com
+ image_url: https://github.com/slorber.png
+ page:
+ # customize the url of the author page at /blog/authors/
+ permalink: '/all-sebastien-lorber-articles'
+ socials:
+ x: sebastienlorber
+ linkedin: sebastienlorber
+ github: slorber
+ newsletter: https://thisweekinreact.com
diff --git a/erc7824-docs/blog/tags.yml b/erc7824-docs/blog/tags.yml
new file mode 100644
index 000000000..bfaa778fb
--- /dev/null
+++ b/erc7824-docs/blog/tags.yml
@@ -0,0 +1,19 @@
+facebook:
+ label: Facebook
+ permalink: /facebook
+ description: Facebook tag description
+
+hello:
+ label: Hello
+ permalink: /hello
+ description: Hello tag description
+
+docusaurus:
+ label: Docusaurus
+ permalink: /docusaurus
+ description: Docusaurus tag description
+
+hola:
+ label: Hola
+ permalink: /hola
+ description: Hola tag description
diff --git a/erc7824-docs/docs/erc-7824.md b/erc7824-docs/docs/erc-7824.md
new file mode 100644
index 000000000..65457ca35
--- /dev/null
+++ b/erc7824-docs/docs/erc-7824.md
@@ -0,0 +1,478 @@
+---
+sidebar_position: 2
+title: ERC-7824
+description: Interfaces and data types for cross-chain stateful asset transfer
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, javascript, typescript, sdk]
+---
+# ERC-7824
+
+## Abstract
+
+Nitrolite is a lightweight, efficient state channel framework for Ethereum and other EVM-compatible blockchains, enabling off-chain interactions while maintaining on-chain security guarantees. The framework allows participants to perform instant transactions with reduced gas costs while preserving the security of the underlying blockchain.
+
+This standard defines a framework for implementing state channel systems through the Nitrolite protocol, providing interfaces for channel creation, state management, dispute resolution, and fund custody. It enables high-throughput applications with minimal on-chain footprint.
+
+## Motivation
+
+The Ethereum network faces challenges in scalability and transaction costs, making it less feasible for high-frequency interactions. The Nitrolite framework addresses these challenges by providing a lightweight state channel solution that enables:
+
+- **Instant Finality**: Transactions settle immediately between parties
+- **Reduced Gas Costs**: Most interactions happen off-chain, with minimal on-chain footprint
+- **High Throughput**: Support for thousands of transactions per second
+- **Security Guarantees**: Same security as on-chain, with cryptographic proofs
+- **Chain Agnostic**: Works with any EVM-compatible blockchain
+
+This ERC standardizes the Nitrolite protocol interfaces to facilitate widespread adoption of state channels.
+
+## Specification
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
+
+### Glossary of Terms
+
+- **Channel**: A relationship between participants that allows off-chain state updates with on-chain settlement
+- **Channel ID**: A unique identifier derived from channel configuration (participants, adjudicator, challenge period, nonce)
+- **Participant**: An entity (EOA) involved in a state channel
+- **State**: A signed data structure containing version, allocations, and application data
+- **Allocation**: Specification of token distribution to destinations
+- **Adjudicator**: Contract that validates state transitions according to application rules
+- **Challenge Period**: Duration for dispute resolution before finalization
+- **Status**: Channel lifecycle stage (VOID, INITIAL, ACTIVE, DISPUTE, FINAL)
+- **State Intent**: Purpose of a state (OPERATE, INITIALIZE, RESIZE, FINALIZE)
+- **Custody**: On-chain contract holding locked funds for channels
+- **Checkpoint**: Recording a valid state on-chain without closing the channel
+
+### Data Structures
+
+The Nitrolite protocol defines the following core data structures:
+
+#### Basic Types
+
+```solidity
+struct Amount {
+ address token; // ERC-20 token address (address(0) for native tokens)
+ uint256 amount; // Token amount
+}
+
+struct Allocation {
+ address destination; // Where funds are sent on channel closure
+ address token; // ERC-20 token contract address (address(0) for native tokens)
+ uint256 amount; // Token amount allocated
+}
+```
+
+#### Channel Configuration
+
+```solidity
+struct Channel {
+ address[] participants; // List of participants in the channel
+ address adjudicator; // Address of the contract that validates state transitions
+ uint64 challenge; // Duration in seconds for dispute resolution period
+ uint64 nonce; // Unique per channel with same participants and adjudicator
+}
+```
+
+#### State Structure
+
+```solidity
+struct State {
+ StateIntent intent; // Intent of the state
+ uint256 version; // State version incremental number to compare most recent
+ bytes data; // Application data encoded, decoded by the adjudicator
+ Allocation[] allocations; // Asset allocation and destination for each participant
+ bytes[] sigs; // stateHash signatures from participants
+}
+
+enum StateIntent {
+ OPERATE, // Normal operation state
+ INITIALIZE, // Initial funding state
+ RESIZE, // Resize allocations state
+ FINALIZE // Final closing state
+}
+```
+
+#### Channel Status
+
+```solidity
+enum Status {
+ VOID, // Channel was not created, State.version must be 0
+ INITIAL, // Channel is created and in funding process, State.version must be 0
+ ACTIVE, // Channel fully funded and operational, State.version > 0
+ DISPUTE, // Challenge period is active
+ FINAL // Final state, channel can be closed
+}
+```
+
+#### Channel ID
+
+The channel ID is computed as:
+
+```solidity
+bytes32 channelId = keccak256(
+ abi.encode(
+ channel.participants,
+ channel.adjudicator,
+ channel.challenge,
+ channel.nonce
+ )
+);
+```
+
+#### State Hash
+
+For signature verification, the state hash is computed as:
+
+```solidity
+bytes32 stateHash = keccak256(
+ abi.encode(
+ channelId,
+ state.intent,
+ state.version,
+ state.data,
+ state.allocations
+ )
+);
+```
+
+Note: The smart contract supports all popular signature formats, specifically: raw ECDSA, EIP-191, EIP-712, EIP-1271, and EIP-6492.
+
+### Interfaces
+
+#### IAdjudicator
+
+Defines the interface for contracts that validate state transitions according to application-specific rules:
+
+```solidity
+interface IAdjudicator {
+ /**
+ * @notice Validates a candidate state based on application-specific rules
+ * @dev Used to determine if a state is valid during challenges or checkpoints
+ * @param chan The channel configuration
+ * @param candidate The proposed state to be validated
+ * @param proofs Array of previous states that provide context for validation
+ * @return valid True if the candidate state is valid according to application rules
+ */
+ function adjudicate(
+ Channel calldata chan,
+ State calldata candidate,
+ State[] calldata proofs
+ ) external view returns (bool valid);
+}
+```
+
+#### IComparable
+
+Interface for determining the ordering between states:
+
+```solidity
+interface IComparable {
+ /**
+ * @notice Compares two states to determine their relative ordering
+ * @dev Returns: -1 if candidate < previous, 0 if equal, 1 if candidate > previous
+ * @param candidate The state being evaluated
+ * @param previous The reference state to compare against
+ * @return result The comparison result
+ */
+ function compare(
+ State calldata candidate,
+ State calldata previous
+ ) external view returns (int8 result);
+}
+```
+
+#### IChannel
+
+The main state channel interface that manages the channel lifecycle:
+
+```solidity
+interface IChannel {
+ // Events
+ event Created(bytes32 indexed channelId, Channel channel, State initial);
+ event Joined(bytes32 indexed channelId, uint256 index);
+ event Opened(bytes32 indexed channelId);
+ event Challenged(bytes32 indexed channelId, uint256 expiration);
+ event Checkpointed(bytes32 indexed channelId);
+ event Resized(bytes32 indexed channelId, int256[] deltaAllocations);
+ event Closed(bytes32 indexed channelId);
+
+ /**
+ * @notice Creates a new channel and initializes funding
+ * @dev The creator must sign the funding state with StateIntent.INITIALIZE
+ * @param ch Channel configuration
+ * @param initial Initial state with StateIntent.INITIALIZE and expected allocations
+ * @return channelId Unique identifier for the created channel
+ */
+ function create(Channel calldata ch, State calldata initial)
+ external returns (bytes32 channelId);
+
+ /**
+ * @notice Allows a participant to join a channel by signing the funding state
+ * @dev Participant must provide signature on the same funding state
+ * @param channelId Unique identifier for the channel
+ * @param index Index of the participant in the channel's participants array
+ * @param sig Signature of the participant on the funding state
+ * @return channelId Unique identifier for the joined channel
+ */
+ function join(bytes32 channelId, uint256 index, bytes calldata sig)
+ external returns (bytes32);
+
+ /**
+ * @notice Finalizes a channel with a mutually signed closing state
+ * @dev Requires all participants' signatures on a state with StateIntent.FINALIZE
+ * @param channelId Unique identifier for the channel
+ * @param candidate The latest known valid state to be finalized
+ * @param proofs Additional states required by the adjudicator
+ */
+ function close(
+ bytes32 channelId,
+ State calldata candidate,
+ State[] calldata proofs
+ ) external;
+
+ /**
+ * @notice Resizes channel allocations with participant agreement
+ * @dev Used for adjusting channel allocations without withdrawing funds
+ * @param channelId Unique identifier for the channel
+ * @param candidate The state with new allocations
+ * @param proofs Supporting states for validation
+ */
+ function resize(
+ bytes32 channelId,
+ State calldata candidate,
+ State[] calldata proofs
+ ) external;
+
+ /**
+ * @notice Initiates or updates a challenge with a signed state
+ * @dev Starts a challenge period during which participants can respond
+ * @param channelId Unique identifier for the channel
+ * @param candidate The state being submitted as the latest valid state
+ * @param proofs Additional states required by the adjudicator
+ * @param challengerSig Signature of the challenger on the candidate state. Must be signed by one of the participants
+ */
+ function challenge(
+ bytes32 channelId,
+ State calldata candidate,
+ State[] calldata proofs,
+ bytes calldata challengerSig
+ ) external;
+
+ /**
+ * @notice Records a valid state on-chain without initiating a challenge
+ * @dev Used to establish on-chain proof of the latest state
+ * @param channelId Unique identifier for the channel
+ * @param candidate The state to checkpoint
+ * @param proofs Additional states required by the adjudicator
+ */
+ function checkpoint(
+ bytes32 channelId,
+ State calldata candidate,
+ State[] calldata proofs
+ ) external;
+}
+```
+
+#### IDeposit
+
+Interface for managing token deposits and withdrawals:
+
+```solidity
+interface IDeposit {
+ /**
+ * @notice Deposits tokens into the contract
+ * @dev For native tokens, the value should be sent with the transaction
+ * @param wallet Address of the account whose ledger is changed
+ * @param token Token address (use address(0) for native tokens)
+ * @param amount Amount of tokens to deposit
+ */
+ function deposit(address wallet, address token, uint256 amount) external payable;
+
+ /**
+ * @notice Withdraws tokens from the contract
+ * @dev Can only withdraw available (not locked in channels) funds
+ * @param wallet Address of the account whose ledger is changed
+ * @param token Token address (use address(0) for native tokens)
+ * @param amount Amount of tokens to withdraw
+ */
+ function withdraw(address wallet, address token, uint256 amount) external;
+}
+```
+
+### Channel Lifecycle
+
+1. **Creation**: Creator constructs channel config and signs initial state with `StateIntent.INITIALIZE`. Second participant are able to join a channel immediately by providing a signature over initial state, and funds will be deducted from their account, if available.
+2. **Joining**: Participants verify the channel and sign the same funding state. This step can be omitted by providing a signature over the initial state when creating the channel. Note, however, that this means that funds will be locked from the participant's balance, while `join(...)` allows to fund the channel from external account.
+3. **Active**: Once fully funded, the channel transitions to active state for off-chain operation
+4. **Off-chain Updates**: Participants exchange and sign state updates according to application logic
+5. **Resolution**:
+ - **Cooperative Close**: All parties sign a final state with `StateIntent.FINALIZE`
+ - **Challenge-Response**: Participant can post a state on-chain and initiate challenge period
+ - **Checkpoint**: Record valid state on-chain without closing for future dispute resolution
+ - **Resize**: Adjust allocations by agreement without closing the channel
+
+### Example Implementation: Remittance Adjudicator
+
+The Remittance adjudicator validates payment transfers between participants:
+
+```solidity
+contract Remittance is IAdjudicator, IComparable {
+ struct Intent {
+ uint8 payer; // Index of the paying participant
+ Amount transfer; // Amount and token being transferred
+ }
+
+ /**
+ * @notice Validates a payment state transition
+ * @dev Checks that the payer has signed and allocations are correct
+ */
+ function adjudicate(
+ Channel calldata chan,
+ State calldata candidate,
+ State[] calldata proofs
+ ) external view returns (bool valid) {
+ // Decode the payment intent
+ Intent memory intent = abi.decode(candidate.data, (Intent));
+
+ // For first state (version 1), need funding state as proof
+ if (candidate.version == 1) {
+ require(proofs.length >= 1, "Missing funding state");
+ State memory funding = proofs[0];
+ require(funding.intent == StateIntent.INITIALIZE, "Invalid funding state");
+ }
+
+ // For subsequent states, need previous state
+ if (candidate.version > 1) {
+ require(proofs.length >= 2, "Missing previous state");
+ State memory previous = proofs[1];
+
+ // Verify state transition
+ require(candidate.version == previous.version + 1, "Invalid version");
+
+ // Verify allocations match the intent
+ require(
+ candidate.allocations[intent.payer].amount ==
+ previous.allocations[intent.payer].amount - intent.transfer.amount,
+ "Invalid payer allocation"
+ );
+
+ uint8 payee = intent.payer == 0 ? 1 : 0;
+ require(
+ candidate.allocations[payee].amount ==
+ previous.allocations[payee].amount + intent.transfer.amount,
+ "Invalid payee allocation"
+ );
+ }
+
+ // Verify payer has signed
+ require(candidate.sigs[intent.payer].v != 0, "Missing payer signature");
+
+ return true;
+ }
+
+ /**
+ * @notice Compares states by version number
+ */
+ function compare(
+ State calldata candidate,
+ State calldata previous
+ ) external view returns (int8 result) {
+ if (candidate.version < previous.version) return -1;
+ if (candidate.version > previous.version) return 1;
+ return 0;
+ }
+}
+```
+
+### Example Usage
+
+```solidity
+// Create a channel between Alice and Bob
+Channel memory channel = Channel({
+ participants: [alice, bob],
+ adjudicator: address(remittanceAdjudicator),
+ challenge: 3600, // 1 hour challenge period
+ nonce: 1
+});
+
+// Create initial funding state
+State memory fundingState = State({
+ intent: StateIntent.INITIALIZE,
+ version: 0,
+ data: "",
+ allocations: [
+ Allocation(alice, tokenAddress, 100 ether),
+ Allocation(bob, tokenAddress, 50 ether)
+ ],
+ sigs: [aliceSignature, bobSignature]
+});
+
+// Alice creates and funds the channel
+bytes32 channelId = custody.create(channel, fundingState);
+
+// Bob joins with his signature
+custody.join(channelId, 1, bobSignature);
+
+// Off-chain: Alice pays Bob 10 tokens
+Intent memory paymentIntent = Intent({
+ payer: 0, // Alice is payer
+ transfer: Amount(tokenAddress, 10 ether)
+});
+
+State memory paymentState = State({
+ intent: StateIntent.OPERATE,
+ version: 1,
+ data: abi.encode(paymentIntent),
+ allocations: [
+ Allocation(alice, tokenAddress, 90 ether),
+ Allocation(bob, tokenAddress, 60 ether)
+ ],
+ sigs: [aliceSignature, bobSignature]
+});
+
+// Either party can checkpoint this state on-chain
+custody.checkpoint(channelId, paymentState, [fundingState]);
+```
+
+## Rationale
+
+The Nitrolite framework addresses critical blockchain scalability challenges through a lightweight state channel design. This standard provides clear interfaces to ensure interoperability while maintaining flexibility for diverse applications.
+
+**Efficiency**: Nitrolite minimizes on-chain footprint by requiring only essential operations (create, join, close) to occur on-chain, with all application logic executed off-chain. This enables instant finality and dramatically reduces gas costs.
+
+**Modularity**: The separation of concerns between the custody contract (IChannel), state validation (IAdjudicator), and fund management (IDeposit) allows developers to implement custom application logic while leveraging battle-tested infrastructure.
+
+**Flexibility**: The adjudicator pattern supports any application logic - from simple payments to complex multi-step protocols. The state intent system (INITIALIZE, OPERATE, RESIZE, FINALIZE) provides clear semantics for different operation types.
+
+**Security**: The challenge-response mechanism ensures that participants can always recover their funds by posting the latest valid state on-chain. The checkpoint feature allows proactive security by recording states without closing channels.
+
+**Chain Agnostic**: The protocol design avoids chain-specific features, enabling deployment across any EVM-compatible blockchain and facilitating cross-chain applications through the clearnode architecture.
+
+## Backwards Compatibility
+
+No backward compatibility issues found. This ERC is designed to coexist with existing standards and can integrate with [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) and [ERC-4337](https://www.erc4337.io/)
+
+## Security Considerations
+
+### On-Chain Security
+
+- **Signature Verification**: All state transitions require valid signatures from participants. The protocol supports signatures of all popular formats, including EIP-191 and EIP-712.
+- **Challenge Period**: The configurable challenge duration provides time for honest participants to respond to invalid states.
+- **Adjudicator Validation**: Custom adjudicators must be carefully audited as they control state transition rules.
+- **Reentrancy Protection**: Implementation should follow checks-effects-interactions pattern, especially in fund distribution.
+
+### Off-Chain Security
+
+- **State Storage**: Participants must securely store all signed states as they may need them for disputes.
+- **Signature Security**: Private keys used for state signing must be protected as compromise allows unauthorized state transitions.
+- **Availability**: Participants must monitor the chain for challenges during the challenge period.
+- **Front-running**: Challenge transactions may be front-run; implementations should consider commit-reveal schemes if needed.
+
+### Implementation Considerations
+
+- The custody contract strictly enforces 2-participant channels in the reference implementation.
+- Adjudicators should validate all state transitions according to application rules.
+- Proper nonce management prevents replay attacks across different channels.
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://github.com/ethereum/ERCs/blob/master/LICENSE.md).
diff --git a/erc7824-docs/docs/index.md b/erc7824-docs/docs/index.md
new file mode 100644
index 000000000..09c3c9d3e
--- /dev/null
+++ b/erc7824-docs/docs/index.md
@@ -0,0 +1,62 @@
+---
+sidebar_position: 1
+title: Introduction
+description: Build scalable web3 applications with state channels using Nitrolite.
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, javascript, typescript, sdk]
+---
+
+import { Card, CardGrid } from '@site/src/components/Card';
+
+# Introduction
+
+Welcome to Nitrolite! Built on the ERC-7824 standard, Nitrolite is a powerful framework that enables developers to build high-performance decentralized applications with near-instant finality.
+
+The following guides will walk you through the complete lifecycle of Nitrolite applications, from client initialization to application sessions. Whether you're building payment systems, games, financial applications, or any use case requiring high-frequency transactions, Nitrolite provides the infrastructure you need.
+
+
+
+
+
+
+
+
+
+
+## Key Features
+
+- **Instant Transactions**: Off-chain operations mean no waiting for block confirmations.
+- **Minimal Gas Fees**: On-chain gas is primarily for channel opening and settlement.
+- **High Throughput**: Capable of handling thousands of transactions per second.
+- **Application Flexibility**: Ideal for games, payment systems, real-time interactions, and more.
+
+## Core SDK Architecture
+
+The Nitrolite SDK is designed with modularity and broad compatibility in mind:
+
+1. **NitroliteClient**: This is the main entry point for developers. It provides a high-level API to manage the lifecycle of state channels, including deposits, channel creation, application session management, and withdrawals.
+
+2. **Nitrolite RPC**: This component handles the secure, real-time, off-chain communication between channel participants and the broker. It's responsible for message signing, verification, and routing.
diff --git a/erc7824-docs/docs/legacy/examples/index.md b/erc7824-docs/docs/legacy/examples/index.md
new file mode 100644
index 000000000..0cd8cd5ca
--- /dev/null
+++ b/erc7824-docs/docs/legacy/examples/index.md
@@ -0,0 +1,13 @@
+---
+sidebar_position: 5
+title: Examples
+description: State Channels tutorials, application examples in solidity and golang
+keywords: [erc7824, statechannels, state channels, ethereum scaling, layer 2, off-chain, tutorial, tictactoe, gaming]
+tags:
+ - erc7824
+ - games
+ - golang
+ - docs
+---
+
+# Example Apps
diff --git a/erc7824-docs/docs/legacy/examples/tictactoe.md b/erc7824-docs/docs/legacy/examples/tictactoe.md
new file mode 100644
index 000000000..bb9eefc0a
--- /dev/null
+++ b/erc7824-docs/docs/legacy/examples/tictactoe.md
@@ -0,0 +1,167 @@
+---
+sidebar_position: 3
+title: Gaming with TicTacToe
+description: Example of out to manage nitro state using go-nitro SDK
+keywords: [erc7824, statechannels, nitro, sdk, development, state channels, ethereum scaling, L2]
+tags:
+ - erc7824
+ - golang
+ - go-nitro
+ - nitro
+ - docs
+---
+# TicTacToe Offchain
+
+Below an example in go lang of usage of our upcoming go lang SDK
+Typescript will be also available soon.
+
+```go
+package main
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/ethereum/go-ethereum/common"
+ ecrypto "github.com/ethereum/go-ethereum/crypto"
+ "github.com/layer-3/clearsync/pkg/signer"
+ "github.com/layer-3/neodax/internal/nitro"
+ "github.com/stretchr/testify/require"
+)
+
+// TicTacToe represents a simple Nitro application implementing a Tic-Tac-Toe game.
+type TicTacToe struct {
+ players []signer.Signer
+ grid [3][3]byte // 3x3 grid for TicTacToe
+
+ ch nitro.Channel
+}
+
+// NewTicTacToe initializes a new TicTacToe instance.
+func NewTicTacToe(players []signer.Signer) *TicTacToe {
+ fp := nitro.FixedPart{
+ Participants: []common.Address{players[0].CommonAddress(), players[1].CommonAddress()},
+ }
+ vp := nitro.VariablePart{}
+ s := nitro.StateFromFixedAndVariablePart(fp, vp)
+
+ return &TicTacToe{
+ players: players,
+ grid: [3][3]byte{},
+ ch: *nitro.NewChannel(s),
+ }
+}
+
+// Definition returns the FixedPart of the Nitro state.
+func (t *TicTacToe) Definition() nitro.FixedPart {
+ return t.ch.FixedPart
+}
+
+// Data returns the VariablePart representing the current game state.
+func (t *TicTacToe) Data(turn uint64) nitro.VariablePart {
+ return nitro.VariablePart{
+ AppData: encodeGrid(t.grid),
+ Outcome: nitro.Exit{},
+ }
+}
+
+func (t *TicTacToe) State(turn uint64) nitro.SignedState {
+ return t.ch.SignedStateForTurnNum[turn]
+}
+
+// Validate checks if the current state is valid.
+func (t *TicTacToe) Validate(turn uint64) bool {
+ // Validate state can be done using the contract artifact
+ return true
+}
+
+func (t *TicTacToe) LatestSupportedState() (nitro.SignedState, error) {
+ return t.ch.LatestSupportedState()
+}
+
+// encodeGrid converts the grid to a byte slice for storage in AppData.
+func encodeGrid(grid [3][3]byte) []byte {
+ var data []byte
+ // Encode using ABI
+ for _, row := range grid {
+ data = append(data, row[:]...)
+ }
+ return data
+}
+
+// MakeMove updates the game state with a player's move.
+func (t *TicTacToe) MakeMove(player byte, x, y int) error {
+ if x < 0 || x > 2 || y < 0 || y > 2 {
+ return errors.New("invalid move: out of bounds")
+ }
+ if t.grid[x][y] != 0 {
+ return errors.New("invalid move: cell already occupied")
+ }
+ t.grid[x][y] = player
+ return nil
+}
+
+func (t *TicTacToe) StartGame() error {
+ preFundState, err := t.ch.State(nitro.PreFundTurnNum)
+ if err != nil {
+ return err
+ }
+
+ for _, p := range t.players {
+ if _, err := t.ch.SignAndAddState(preFundState.State(), p); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func (t *TicTacToe) FinishGame() error {
+ lss, err := t.ch.LatestSignedState()
+ if err != nil {
+ return err
+ }
+
+ lastState := lss.State().Clone()
+ lastState.IsFinal = true
+ lastState.TurnNum += 1
+
+ for _, p := range t.players {
+ if _, err := t.ch.SignAndAddState(lastState, p); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func TestTicTacToe(t *testing.T) {
+ // Create player signers
+ var alice, bob signer.Signer
+
+ app := NewTicTacToe([]signer.Signer{alice, bob})
+ nch, err := nitro.NewClient() // Implements channel.Client
+ require.NoError(t, err, "Error creating client")
+
+ err = app.StartGame()
+ require.NoError(t, err, "Error starting game")
+
+ // Open the channel
+ _, err = nch.Open(app)
+ require.NoError(t, err, "Error opening channel")
+
+ // Simulate moves
+ err = app.MakeMove('X', 0, 0)
+ require.NoError(t, err, "Invalid move")
+
+ err = app.MakeMove('O', 1, 1)
+ require.NoError(t, err, "Invalid move")
+
+ err = app.FinishGame()
+ require.NoError(t, err, "Error finishing game")
+
+ // Close the channel at turn 2
+ err = nch.Close(app)
+ require.NoError(t, err, "Error closing channel")
+}
+```
diff --git a/erc7824-docs/docs/legacy/faq.md b/erc7824-docs/docs/legacy/faq.md
new file mode 100644
index 000000000..a547155a5
--- /dev/null
+++ b/erc7824-docs/docs/legacy/faq.md
@@ -0,0 +1,128 @@
+---
+sidebar_position: 7
+title: FAQ
+description: State channels frequently asked questions
+keywords: [erc7824, statechannels, nitro, sdk, faq, state channels, ethereum scaling, L2]
+tags:
+ - erc7824
+ - nitro
+ - faq
+---
+# FAQ
+
+### State channels FAQ
+
+
+ What is ERC-7824?
+
+ ERC-7824 is a proposed standard for cross-chain trade execution systems that use state channels. It defines structures and interfaces to enable efficient, secure, and scalable off-chain interactions while leveraging the blockchain for finality and dispute resolution.
+
+
+
+
+ What is a state channel?
+
+ A state channel can be thought of as an account with multiple balances (often just two). The owners of that account can update those balances according to predefined rules, which are enforceable on a blockchain. This enables peer-to-peer games, payments, and other few-user applications to safely trade blockchain assets with extremely low latency, low cost, and high throughput without requiring trust in a third party.
+
+
+
+
+ How do state channels work?
+
+ 1. Setup: Participants lock assets into a blockchain-based smart contract.
+ 2. Off-Chain Updates: Transactions or updates occur off-chain through cryptographically signed messages.
+ 3. Finalization: The final state is submitted on-chain for settlement, or disputes are resolved if necessary.
+
+
+
+
+ What are the benefits of state channels?
+
+ - High Performance: Transactions are processed off-chain, providing low latency and high throughput.
+ - Cost Efficiency: Minimal blockchain interactions significantly reduce gas fees.
+ - Privacy: Off-chain interactions keep intermediate states confidential.
+ - Flexibility: Supports a wide range of applications, including multi-chain trading.
+
+
+
+
+ What kind of applications use state channels?
+
+ State channels enable the redistribution of assets according to arbitrary logic, making them suitable for:
+
+
Games: Peer-to-peer poker or other interactive games.
+
Payments: Microtransactions and conditional payments.
+ - On-Chain Components: Implemented in Solidity and included in the npm package @statechannels/nitro-protocol.
+ - Off-Chain Components: A reference implementation provided through go-nitro, a lightweight client written in Go.
+
+
+
+
+ Where is Nitro Protocol being used?
+
+ The maintainers of Nitro Protocol are actively integrating it into the Filecoin Retrieval Market and the Filecoin Virtual Machine, enabling decentralized and efficient content distribution.
+
+
+
+
+ What is the structure of a state in state channels?
+
+ A state consists of:
+
+
Fixed Part: Immutable properties like participants, nonce, app definition, and challenge duration.
+
Variable Part: Changeable properties like outcomes, application data, and turn numbers.
+
+ In Nitro, participants sign a keccak256 hash of both these parts to commit to a particular state. The turnNum determines the version of the state, while isFinal can trigger an instant finalization when fully countersigned.
+
+
+
+
+ What is a challenge duration?
+
+ The challenge duration is a time window during which disputes can be raised on-chain. If no disputes are raised, the state channel finalizes according to its latest agreed state. In Nitro, it is set at channel creation and cannot be changed later. During this period, an unresponsive or dishonest participant can be forced to progress the channel state via on-chain transactions.
+
+
+
+
+ How do disputes get resolved in state channels?
+
+ Participants can:
+
+
Submit signed updates to the blockchain as evidence.
+
Resolve disputes based on turn numbers and application-specific rules stored in an on-chain appDefinition.
+
Finalize the channel after the challenge duration if no valid disputes arise.
+
+ Nitro Protocol introduces a challenge mechanism, enabling any participant to push the channel state on-chain, forcing the other side to respond. This ensures that unresponsive or malicious actors cannot stall the channel indefinitely.
+
+
+
+
+ What is the typical channel lifecycle in Nitro Protocol?
+
+ A direct channel often follows these stages:
+
+ 1. Proposed: A participant signs the initial (prefund) state with turnNum=0.
+ 2. ReadyToFund: All participants countersign the prefund state, ensuring it is safe to deposit on-chain.
+ 3. Funded: Deposits appear on-chain, and participants exchange a postfund state (turnNum=1).
+ 4. Running: The channel can be updated off-chain by incrementing turnNum and exchanging signatures.
+ 5. Finalized: A state with isFinal=true is fully signed. No more updates are possible; the channel can pay out according to the final outcome.
+
+
+
+
+ How do I finalize and withdraw funds from a direct channel in Nitro?
+
+ - Finalization (Happy Path): If a fully signed state with isFinal=true exists off-chain, any participant can call conclude on the Nitro Adjudicator to finalize instantly.
+ - Finalization (Dispute Path): If participants are unresponsive or disagree, one party can challenge with the latest supported state. After the challenge window, the channel is finalized if unchallenged or out-of-date states are resolved.
+ - Withdrawing: Once finalized, participants use the transfer or concludeAndTransferAllAssets method to claim their allocations on-chain.
+
+
diff --git a/erc7824-docs/docs/legacy/index.md b/erc7824-docs/docs/legacy/index.md
new file mode 100644
index 000000000..9680d9e2c
--- /dev/null
+++ b/erc7824-docs/docs/legacy/index.md
@@ -0,0 +1,69 @@
+---
+sidebar_position: 5
+slug: /legacy
+title: Legacy
+description: Create high-performance chain agnostic dApps using state channels
+keywords: [erc7824, statechannels, chain abstraction, chain agnostic, state channels, ethereum scaling, layer 2, layer 3, nitro, trading, high-speed]
+tags:
+ - erc7824
+ - docs
+---
+
+# Inject Nitro in your Stack
+
+Blockchain technology has introduced a paradigm shift in how digital assets and decentralized applications (dApps) operate. However, scalability, transaction costs, and cross-chain interoperability remain significant challenges. **ERC-7824** and the **Nitro Protocol** provide a solution by enabling **off-chain state channels**, allowing seamless and efficient transactions without compromising on security or performance.
+
+ERC-7824 defines a minimal, universal interface for state channel interactions without assuming any specific underlying chain, making it naturally chain agnostic. It abstracts away chain-specific details by specifying data structures and messaging formats that can be verified on any blockchain with basic smart contract capabilities. As a result, developers can rely on ERC-7824’s standard approach for off-chain interactions and dispute resolution across multiple L1 or L2 networks, preserving interoperability and reusability of state channel components in a wide range of environments.
+
+## Supercharging Web2/3 Applications
+
+The Nitro Protocol is designed to integrate effortlessly with **both Web2 and Web3 applications**. Whether operating in traditional infrastructure or leveraging blockchain ecosystems, developers can **enhance their software stack** with blockchain-grade security and efficiency without sacrificing speed or requiring major architectural changes.
+
+```mermaid
+quadrantChart
+ title Distributed system scaling
+ x-axis Low Speed --> High Speed
+ y-axis Low Trust --> High Trust
+ quadrant-1 Network Overlay
+ quadrant-2 Decentralized Systems
+ quadrant-3 Cross-chain Systems
+ quadrant-4 Centralized Systems
+ Bitcoin: [0.20, 0.8]
+ L2 Chains: [0.57, 0.70]
+ L3 Nitro: [0.75, 0.60]
+ ClearSync: [0.80, 0.85]
+ Solana: [0.60, 0.42]
+ TradFi: [0.85, 0.34]
+ CEX: [0.70, 0.20]
+ Bridges: [0.24, 0.24]
+ Cross-chain Messaging: [0.15, 0.40]
+ Ethereum: [0.35, 0.78]
+```
+
+## Key Benefits of ERC-7824 and Nitro Protocol
+
+1. **Scalability Without Congestion**
+ By moving most interactions off-chain and leveraging **state channels**, Nitro enables **near-instant, gas-free transactions**, reducing load on the base layer blockchain.
+
+2. **Cost Efficiency**
+ Users and applications **avoid high gas fees** by settling transactions off-chain while maintaining an on-chain fallback for dispute resolution.
+
+3. **Chain Agnosticism & Interoperability**
+ ERC-7824 is designed to be **cross-chain compatible**, allowing seamless **interactions between different blockchain ecosystems**. This enables **cross-chain asset transfers, atomic swaps, and multi-chain smart contract execution**.
+
+4. **Security & Trustless Execution**
+ - Transactions occur off-chain but remain **cryptographically signed and enforceable** on-chain.
+ - **ForceMove** dispute resolution mechanism ensures that **malicious actors cannot manipulate state transitions**.
+
+5. **Modular & Flexible**
+ The protocol provides **plug-and-play interfaces**, allowing developers to build **custom business logic** while ensuring compatibility with the ERC-7824 framework.
+
+6. **Real-World Applications**
+ - **DeFi Scaling:** Enables off-chain transactions and batch settlements for **DEXs and lending protocols**.
+ - **Gaming & Metaverse:** Supports **high-speed microtransactions** for in-game economies.
+ - **Cross-Chain Payments:** Facilitates **low-cost, instant remittances and global transactions**.
+ - **Enterprise & Supply Chain:** Enhances **multi-party agreements and digital asset settlement**.
+
+## Summary
+
+ERC-7824 and the Nitro Protocol provide the **next-generation infrastructure** for decentralized applications, bridging the gap between **legacy systems and blockchain**. By combining **off-chain speed** with **on-chain security**, this framework allows developers to build high-performance, scalable solutions without being tied to a single blockchain network.
diff --git a/erc7824-docs/docs/legacy/protocol.md b/erc7824-docs/docs/legacy/protocol.md
new file mode 100644
index 000000000..6f638dca8
--- /dev/null
+++ b/erc7824-docs/docs/legacy/protocol.md
@@ -0,0 +1,430 @@
+---
+sidebar_position: 3
+title: Protocol
+description: Interfaces and nitro types for NitroApp development
+keywords: [erc7824, statechannels, nitro, protocol, sdk, development, state channels, ethereum scaling, L2]
+tags:
+ - erc7824
+ - protocol
+ - nitro
+ - sdk
+ - docs
+---
+
+# Nitro Protocol
+
+**Nitro Protocol** is a framework for building fast, secure, and flexible state channels on Ethereum. It enables developers to keep most transactions off-chain for near-instant updates while retaining the option to settle disputes on-chain as a last resort. At the heart of Nitro are well-defined states and transition rules enforced by the on-chain Adjudicator contract. The application-specific logic (“NitroApp”) is encapsulated in an on-chain contract, which the Nitro Adjudicator references during disputes to verify that a proposed state transition is valid.
+
+Off-chain, developers write the code responsible for constructing and signing new states, managing state progression, and orchestrating deposits and withdrawals. This off-chain logic ensures quick state updates without paying on-chain gas fees, since new states only require on-chain submission when there is a disagreement or final payout. By following Nitro’s API and implementing these components carefully, developers can build robust applications that benefit from low latency and trust-minimized on-chain settlements.
+
+## Overview
+
+```mermaid
+graph TD
+ subgraph Nitro Protocol
+ INitroAdjudicator[INitroAdjudicator] -->|Interacts with| IForceMove[IForceMove]
+ INitroAdjudicator -->|Manages outcomes| IMultiAssetHolder[IMultiAssetHolder]
+ INitroAdjudicator -->|Handles states| IStatusManager[IStatusManager]
+
+ IForceMove -->|Implements state transitions| IForceMoveApp[IForceMoveApp]
+ IForceMoveApp -->|Validates states| INitroTypes[INitroTypes]
+ end
+
+ subgraph NitroApps
+ CountingApp[CountingApp] -->|Implement| IForceMoveApp
+ EscrowApp[VEscrowApp] -->|Implement| IForceMoveApp
+ end
+```
+
+## 1. States & Channels
+
+A state channel can be thought of as a private ledger containing balances and other arbitrary data housed in a data structure called a “state.” The state of the channel is updated, committed to (via signatures), and exchanged between a fixed set of actors (participants). A state channel controls funds which are locked — typically on an L1 blockchain — and those locked funds are unlocked according to the channel’s final state when the channel finishes.
+
+### States
+
+In Nitro protocol, a state is broken up into fixed and variable parts:
+
+
+Solidity
+
+```solidity
+import {ExitFormat as Outcome} from '@statechannels/exit-format/contracts/ExitFormat.sol';
+
+struct FixedPart {
+ address[] participants;
+ uint48 channelNonce;
+ address appDefinition;
+ uint48 challengeDuration;
+}
+
+struct VariablePart {
+ Outcome.SingleAssetExit[] outcome; // (1)
+ bytes appData;
+ uint48 turnNum;
+ bool isFinal;
+}
+```
+
+
+
+
+TypeScript
+
+```typescript
+import * as ExitFormat from '@statechannels/exit-format';
+// (1)
+import {Address, Bytes, Bytes32, Uint256, Uint48, Uint64} from '@statechannels/nitro-protocol';
+
+export interface FixedPart {
+ participants: Address[];
+ channelNonce: Uint64;
+ appDefinition: Address;
+ challengeDuration: Uint48;
+}
+
+export interface VariablePart {
+ outcome: ExitFormat.Exit; // (2)
+ appData: Bytes;
+ turnNum: Uint48;
+ isFinal: boolean;
+}
+```
+
+
+
+
+Go
+
+```go
+import (
+ "github.com/statechannels/go-nitro/channel/state/outcome"
+ "github.com/statechannels/go-nitro/types" // (1)
+)
+
+type (
+ FixedPart struct {
+ Participants []types.Address
+ ChannelNonce uint64
+ AppDefinition types.Address
+ ChallengeDuration uint32
+ }
+
+ VariablePart struct {
+ AppData types.Bytes
+ Outcome outcome.Exit // (2)
+ TurnNum uint64
+ IsFinal bool
+ }
+)
+```
+
+
+
+1. `Bytes32`, `Bytes`, `Address`, `Uint256`, `Uint64` are aliases for hex-encoded strings. `Uint48` is aliased to a `number`.
+2. This composite type is explained in more detail in the “Outcomes” section.
+
+Each state has:
+
+- **Fixed Part**:
+ - `participants`: The list of addresses (one per participant).
+ - `channelNonce`: A unique identifier so that re-using the same participants and app definition does not cause replay attacks.
+ - `appDefinition`: Address of the application contract (on-chain code) that enforces application-specific rules.
+ - `challengeDuration`: The duration (in seconds) of the challenge window in case of on-chain disputes.
+
+- **Variable Part**:
+ - `outcome`: The distribution of funds if the channel were finalized in that state.
+ - `appData`: Extra data interpreted by the application (e.g. game state).
+ - `turnNum`: A version counter for the state updates.
+ - `isFinal`: A boolean that triggers instant finalization when fully signed.
+
+#### Channel IDs
+
+Channels are identified by the hash of the fixed part:
+
+```solidity
+bytes32 channelId = keccak256(
+ abi.encode(
+ fixedPart.participants,
+ fixedPart.channelNonce,
+ fixedPart.appDefinition,
+ fixedPart.challengeDuration
+ )
+);
+```
+
+#### State Commitments
+
+To commit to a state, we take the keccak256 hash of a combination of the channel ID and the variable part:
+
+```solidity
+bytes32 stateHash = keccak256(abi.encode(
+ channelId,
+ variablePart.appData,
+ variablePart.outcome,
+ variablePart.turnNum,
+ variablePart.isFinal
+));
+```
+
+Participants sign this `stateHash` using their private key. A signature has the form `(v, r, s)` in ECDSA.
+
+#### Signed Variable Parts and Support Proofs
+
+A “signed variable part” is simply the variable part plus the signatures from participants. Submitting such bundles to the chain allows the chain to verify that a given state is “supported” off-chain. Typically, a single channel update is accompanied by a single “candidate” state plus (optionally) a sequence of older states that prove the channel progressed correctly. This bundle is called a “support proof.”
+
+---
+
+## 2. Execution Rules
+
+A channel’s execution rules dictate which state updates can be considered valid and which are invalid. Once a state is considered “supported” (i.e., it has enough signatures to pass on-chain checks), it can become the channel’s final state if the channel is later finalized.
+
+### Core Protocol Rules
+
+1. **Higher Turn Number**: Among multiple states, the one with the highest turn number supersedes the rest.
+2. **Instant Finalization**: If a state has `isFinal = true` and is fully signed, it can finalize the channel without needing a challenge phase.
+
+### Application Rules
+
+Each channel references an on-chain contract (the `appDefinition`) that encodes custom logic:
+
+```solidity
+interface IForceMoveApp is INitroTypes {
+ function stateIsSupported(
+ FixedPart calldata fixedPart,
+ RecoveredVariablePart[] calldata proof,
+ RecoveredVariablePart calldata candidate
+ ) external view returns (bool, string memory);
+}
+```
+
+When on-chain disputes occur, the chain calls this function to confirm that a “candidate” state is valid relative to the “proof” states.
+
+---
+
+## 3. Outcomes
+
+The **outcome** of a state is the piece that dictates how funds in the channel will be disbursed once the channel is finalized. Nitro uses the [L2 exit format](https://github.com/statechannels/exit-format):
+
+- **Outcome**: An array of `SingleAssetExit`, each describing:
+ - An `asset` (e.g. the zero address for native ETH, or an ERC20 contract address),
+ - Optional `assetMetadata`,
+ - A list of **allocations**.
+
+Example:
+
+```ts
+import {
+ Exit,
+ SingleAssetExit,
+ NullAssetMetadata,
+ AllocationType,
+} from "@statechannels/exit-format";
+
+const ethExit: SingleAssetExit = {
+ asset: "0x0", // native token (ETH)
+ assetMetadata: NullAssetMetadata,
+ allocations: [
+ {
+ destination: "0x00000000000000000000000096f7123E3A80C9813eF50213ADEd0e4511CB820f",
+ amount: "0x05",
+ allocationType: AllocationType.simple,
+ metadata: "0x",
+ },
+ {
+ destination: "0x0000000000000000000000000737369d5F8525D039038Da1EdBAC4C4f161b949",
+ amount: "0x05",
+ allocationType: AllocationType.simple,
+ metadata: "0x",
+ },
+ ],
+};
+
+const exit = [ethExit];
+```
+
+### Allocations
+
+Allocations specify a `destination` and an `amount`. For **direct channels**, these allocations are typically of `allocationType = 0` (simple). Each entry is the portion of the channel’s asset that the channel participant can claim once the channel is finalized on-chain.
+
+### Destinations
+
+A destination is a 32-byte value that may represent:
+
+- A channel ID, or
+- An “external destination” (an Ethereum address left-padded with zeros).
+
+For **direct** channels, the relevant destinations are ordinarily external addresses corresponding to participant wallets.
+
+---
+
+## 4. Lifecycle of a Channel
+
+Below we describe the lifecycle of a **direct** channel.
+
+### Off-Chain Lifecycle (High-Level)
+
+1. **Proposed**: A participant signs the prefund state (`turnNum = 0`) and shares it with others.
+2. **ReadyToFund**: Once all have countersigned, the channel can safely be funded on-chain.
+3. **Funded**: After the postfund state (`turnNum = 1`) is signed, the channel is recognized as funded.
+4. **Running**: States with `turnNum > 1` can be signed as the channel operates.
+5. **Finalized**: A state with `isFinal = true` is fully signed.
+
+A diagram (off-chain states):
+
+```mermaid
+stateDiagram-v2
+ [*] --> Proposed
+ Proposed --> ReadyToFund
+ ReadyToFund --> Funded
+ Funded --> Running
+ Running --> Finalized
+```
+
+### Funding Lifecycle (On-Chain)
+
+A channel is “funded” on-chain once participants’ deposits have been made to the adjudicator contract. Eventually, once the channel is finished, those funds are withdrawn to participants’ external addresses.
+
+```mermaid
+stateDiagram-v2
+state OnChain {
+ state Funding {
+ [*]-->NotFundedOnChain
+ NotFundedOnChain --> FundedOnChain
+ FundedOnChain --> NotFundedOnChain
+ }
+}
+```
+
+- **NotFundedOnChain** → channel has 0 or insufficient deposit in the adjudicator.
+- **FundedOnChain** → channel has the full deposit allocated.
+
+### Adjudication Lifecycle (On-Chain)
+
+Nitro’s `NitroAdjudicator` contract tracks the channel’s status on-chain:
+
+1. **Open**: No challenge is in progress.
+2. **Challenge**: A challenge is ongoing; participants must respond or let it expire.
+3. **Finalized**: The channel has either concluded with a `conclude` transaction or the challenge timed out.
+
+```mermaid
+stateDiagram-v2
+state OnChain {
+state Adjudication {
+Open -->Challenge: challenge
+Open --> Open: checkpoint
+Open--> Finalized: conclude
+Challenge--> Challenge: challenge
+Challenge--> Open: checkpoint
+Challenge--> Finalized: conclude
+Challenge--> Finalized': timeout
+}
+}
+```
+
+---
+
+## 5. Precautions and Limits
+
+### Precautions
+
+As a participant, you should verify:
+
+- The number of participants is correct.
+- Your own address is in `participants`.
+- The `channelNonce` is unique.
+- `challengeDuration` is acceptable.
+
+### Limits
+
+There are upper bounds to how large a state can be before it becomes impractical to finalize on-chain:
+
+- `MAX_TX_DATA_SIZE`: ~128 KB typical limit for Ethereum transaction data.
+- `NITRO_MAX_GAS`: ~6M gas, a safe upper bound for some Nitro operations.
+- `MAX_OUTCOME_ITEMS`: The protocol recommends limiting the number of allocation items to at most 2000 to remain under `MAX_TX_DATA_SIZE` and `NITRO_MAX_GAS`.
+
+---
+
+## 6. Funding a Channel
+
+This section covers **on-chain** depositing for direct channels.
+
+### Fund with an On-Chain `deposit`
+
+When participants want to stake funds, they each perform a deposit transaction to the `NitroAdjudicator`:
+
+```solidity
+function deposit(
+ address asset,
+ bytes32 destination,
+ uint256 expectedHeld,
+ uint256 amount
+) public payable
+```
+
+- `asset`: zero address if depositing ETH, or an ERC20 token address if depositing tokens.
+- `destination`: must be the channel’s `channelId` (not an external address).
+- `expectedHeld`: ensures the holdings so far match what you expect, preventing over-funding or front-running.
+- `amount`: how much is being deposited in this transaction.
+
+If depositing ETH, include `{value: amount}` in the transaction call. If depositing ERC20 tokens, you must first `approve` the `NitroAdjudicator` for `amount`.
+
+#### Outcome Priority
+
+It is possible for a channel to be underfunded if not all participants have deposited yet. If a channel finalizes underfunded, the distribution is paid in priority order (the topmost allocation entries get paid first). Thus, participants commonly wait until they each see the correct deposit events on-chain before signing postfund states.
+
+---
+
+## 7. Finalizing a Channel
+
+A channel is **finalized** either off-chain with unanimous agreement or via an on-chain transaction (the “happy path” or the “sad path”).
+
+### Happy Path (Off-Chain)
+
+- One participant proposes a state with `isFinal = true`.
+- All participants sign it.
+- No on-chain action is strictly needed to *logically* finalize the channel (they can defund off-chain if no on-chain deposit was used).
+
+### On-Chain – Calling `conclude`
+
+When the channel was funded on-chain, any participant (or anyone with the finalization proof) can call:
+
+```solidity
+function conclude(
+ FixedPart memory fixedPart,
+ SignedVariablePart memory candidate
+) external
+```
+
+This finalizes the channel’s outcome **instantly** on-chain (no challenge period). Afterward, participants can withdraw or `transfer` funds out of the adjudicator contract.
+
+---
+
+## 8. Defunding a Channel
+
+Once a channel is **finalized**, its locked funds can be released. For **direct** channels, this typically means:
+
+### On-Chain Defunding Using `transfer`
+
+The on-chain `transfer` method in the `NitroAdjudicator` contract moves funds from the channel’s escrow to participant addresses:
+
+```solidity
+function transfer(
+ uint256 assetIndex,
+ bytes32 channelId,
+ bytes memory outcomeBytes,
+ bytes32 stateHash,
+ uint256[] memory indices
+) public
+```
+
+- `assetIndex`: Which asset in the channel’s outcome you want to pay out.
+- `channelId`: The identifier of the channel.
+- `outcomeBytes`: The encoded outcome (list of allocations).
+- `stateHash`: If finalization was done via `conclude`, you may pass `bytes32(0)`.
+- `indices`: Which allocations to pay out in this transaction (`[]` means “all”).
+
+Once called, the relevant allocations are paid out to the listed `destination`s, and the contract’s tracked holdings are reduced accordingly. Multiple calls may be required to pay out all allocations, especially if there are many recipients.
+
+#### concludeAndTransferAllAssets
+
+The contract also offers a “batched” convenience method `concludeAndTransferAllAssets` that finalizes and transfers in a single transaction.
diff --git a/erc7824-docs/docs/legacy/resources.md b/erc7824-docs/docs/legacy/resources.md
new file mode 100644
index 000000000..263e6b44a
--- /dev/null
+++ b/erc7824-docs/docs/legacy/resources.md
@@ -0,0 +1,32 @@
+---
+sidebar_position: 4
+title: Resources
+description: Learn more about state channels
+keywords: [erc7824, statechannels, state channels, awesome state channels, ethereum scaling, layer 2, layer 3, nitro, trading, high-speed]
+tags:
+ - erc7824
+ - links
+ - docs
+---
+
+# Resources
+
+### General Overviews
+
+- [What are State Channels? (Ethereum Foundation)](https://ethereum.org/en/developers/docs/scaling/state-channels/)
+- [State Channels vs. Rollups (Vitalik Buterin)](https://vitalik.eth.limo/general/2021/01/05/rollup.html)
+
+### Technical Papers
+
+- [(2018) ForceMove: An n-party state channel protocol](https://magmo.com/force-move-games.pdf)
+- [(2019) Nitro Protocol: Virtual channels](https://magmo.com/nitro-protocol.pdf)
+- [(2022) Stateful Asset Transfer Protocol](https://statechannels.github.io/satp_paper/satp.pdf)
+- [Nitro Protocol GitHub Repository](https://github.com/statechannels/go-nitro)
+
+### Videos and Tutorials
+
+- [State Channels Explained (YouTube)](https://www.youtube.com/watch?v=p1OTfvCbRFo)
+- [Nitro Protocol Walkthrough (YouTube)](https://www.youtube.com/watch?v=Z5dcPAfIzP4)
+- [Ethereum Magicians State Channel Discussion](https://ethereum-magicians.org/c/scaling/state-channels)
+- [Protocol Tutorial](https://docs.statechannels.org/protocol-tutorial/0010-states-channels/)
+- [State Channels Blog](https://blog.statechannels.org/)
diff --git a/erc7824-docs/docs/legacy/spec.md b/erc7824-docs/docs/legacy/spec.md
new file mode 100644
index 000000000..0c79e4e62
--- /dev/null
+++ b/erc7824-docs/docs/legacy/spec.md
@@ -0,0 +1,355 @@
+---
+sidebar_position: 2
+title: Specification
+description: Request for Comment on statechannels framework
+keywords: [erc7824, ERC, Ethereum, statechannels, nitro, sdk, development, state channels, ethereum scaling, L2]
+tags:
+ - erc7824
+ - nitro
+ - docs
+---
+
+# ERC-7824
+
+## Abstract
+
+State Channels is allowing participants to perform off-chain transactions while maintaining the security guarantees of the Ethereum blockchain. The goal is to enhance scalability and reduce transaction costs for decentralized applications.
+
+This standard defines a framework for implementing state channel systems, enabling efficient off-chain transaction execution and dispute resolution. It provides interfaces for managing channel states and an example implementation.
+
+## Motivation
+
+The Ethereum network faces challenges in scalability and transaction costs, making it less feasible for high-frequency interactions. State Channels provide a mechanism to perform most interactions off-chain, reserving on-chain operations for dispute resolution or final settlement. This ERC facilitate the adoption of state channels by standardizing essential interfaces.
+
+## Specification
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
+
+### Glossary of Terms
+
+- **Channel**: A construct where participants interact off-chain using signed messages.
+- **Channel ID**: A unique identifier for a channel, derived from the channel's fixed part.
+- **Participant**: An entity (EOA) involved in a state channel.
+- **Turn Number**: A counter indicating the sequence of moves in the channel.
+- **State**: A representation of the channel's condition, updated via signed messages.
+- **Fixed Part**: Immutable channel parameters.
+- **Variable Part**: Mutable channel parameters that evolve as states are updated.
+- **ForceMove**: A procedure for resolving disputes by transitioning the state on-chain.
+- **Outcome**: The final result of a channel state used for settling balances.
+- **Nitro**: Historial name of the initial protocol
+
+### Data Structures
+
+#### ExitFormat
+
+Standard data structure for exiting an EVM based state channel.
+
+```solidity
+/// @title ExitFormat
+/// @notice Standard for encoding channel outcomes
+library ExitFormat {
+ struct SingleAssetExit {
+ address asset;
+ AssetMetadata assetMetadata;
+ Allocation[] allocations;
+ }
+
+ // AssetMetadata allows for different token standards
+ struct AssetMetadata {
+ AssetType assetType;
+ bytes metadata;
+ }
+
+ enum AssetType {Default, ERC721, ERC1155, Qualified}
+
+ enum AllocationType {simple, withdrawHelper, guarantee}
+
+ struct Allocation {
+ bytes32 destination;
+ uint256 amount;
+ uint8 allocationType;
+ bytes metadata;
+ }
+}
+```
+
+#### NitroTypes
+
+The `FixedPart`, `VariablePart` compose a "state". The state of the channel is updated, committed to and exchanged between a fixed set of participants.
+
+```solidity
+/// @title NitroTypes
+/// @notice Defines the core data structures used in state channels.
+interface INitroTypes {
+ struct FixedPart {
+ address[] participants;
+ uint64 channelNonce; // This is a unique number used to differentiate channels
+ address appDefinition; // This is an Ethereum address where a ForceMoveApp has been deployed
+ uint48 challengeDuration; // This is duration in seconds of the challenge-response window
+ }
+
+ struct VariablePart {
+ Outcome.SingleAssetExit[] outcome;
+ bytes appData;
+ uint48 turnNum;
+ bool isFinal; // This is a boolean flag which allows the channel to be finalized "instantly"
+ }
+
+ struct SignedVariablePart {
+ VariablePart variablePart;
+ Signature[] sigs;
+ }
+
+ struct RecoveredVariablePart {
+ VariablePart variablePart;
+ uint256 signedBy; // bitmask
+ }
+}
+```
+
+#### Channel ID
+
+A `channelId` is derived using:
+
+```solidity
+bytes32 channelId = keccak256(
+ abi.encode(
+ fixedPart.participants,
+ fixedPart.channelNonce,
+ fixedPart.appDefinition,
+ fixedPart.challengeDuration
+ )
+);
+```
+
+## State commitments
+
+To commit to a state, a hash is formed as follows:
+
+```solidity
+bytes32 stateHash = keccak256(abi.encode(
+ channelId,
+ variablePart.appData,
+ variablePart.outcome,
+ variablePart.turnNum,
+ variablePart.isFinal
+));
+```
+
+### Interfaces
+
+#### IForceMoveApp
+
+Define the state machine of a ForceMove state channel protocol
+
+```solidity
+/// @title IForceMoveApp
+/// @notice Interface for implementing protocol rules in state channels
+interface IForceMoveApp is INitroTypes {
+ // @notice Encodes application-specific rules for a particular ForceMove-compliant state channel. Must revert or return false when invalid support proof and a candidate are supplied.
+ // @dev Depending on the application, it might be desirable to narrow the state mutability of an implementation to 'pure' to make security analysis easier.
+ // @param fixedPart Fixed part of the state channel.
+ // @param proof Array of recovered variable parts which constitutes a support proof for the candidate. May be omitted when `candidate` constitutes a support proof itself.
+ // @param candidate Recovered variable part the proof was supplied for. Also may constitute a support proof itself.
+ function stateIsSupported(
+ FixedPart calldata fixedPart,
+ RecoveredVariablePart[] calldata proof,
+ RecoveredVariablePart calldata candidate
+ ) external view returns (bool, string memory);
+}
+```
+
+#### IForceMove
+
+The IForceMove interface defines the interface that an implementation of ForceMove should implement.
+
+```solidity
+interface IForceMove is INitroTypes {
+ /**
+ * @notice Registers a challenge against a state channel. A challenge will either prompt another participant into clearing the challenge (via one of the other methods), or cause the channel to finalize at a specific time.
+ * @dev Registers a challenge against a state channel. A challenge will either prompt another participant into clearing the challenge (via one of the other methods), or cause the channel to finalize at a specific time.
+ * @param fixedPart Data describing properties of the state channel that do not change with state updates.
+ * @param proof Additional proof material (in the form of an array of signed states) which completes the support proof.
+ * @param candidate A candidate state (along with signatures) which is being claimed to be supported.
+ * @param challengerSig The signature of a participant on the keccak256 of the abi.encode of (supportedStateHash, 'forceMove').
+ */
+ function challenge(
+ FixedPart memory fixedPart,
+ SignedVariablePart[] memory proof,
+ SignedVariablePart memory candidate,
+ Signature memory challengerSig
+ ) external;
+
+ /**
+ * @notice Overwrites the `turnNumRecord` stored against a channel by providing a candidate with higher turn number.
+ * @dev Overwrites the `turnNumRecord` stored against a channel by providing a candidate with higher turn number.
+ * @param fixedPart Data describing properties of the state channel that do not change with state updates.
+ * @param proof Additional proof material (in the form of an array of signed states) which completes the support proof.
+ * @param candidate A candidate state (along with signatures) which is being claimed to be supported.
+ */
+ function checkpoint(
+ FixedPart memory fixedPart,
+ SignedVariablePart[] memory proof,
+ SignedVariablePart memory candidate
+ ) external;
+
+ /**
+ * @notice Finalizes a channel according to the given candidate. External wrapper for _conclude.
+ * @dev Finalizes a channel according to the given candidate. External wrapper for _conclude.
+ * @param fixedPart Data describing properties of the state channel that do not change with state updates.
+ * @param candidate A candidate state (along with signatures) to change to.
+ */
+ function conclude(FixedPart memory fixedPart, SignedVariablePart memory candidate) external;
+
+ // events
+
+ /**
+ * @dev Indicates that a challenge has been registered against `channelId`.
+ * @param channelId Unique identifier for a state channel.
+ * @param finalizesAt The unix timestamp when `channelId` will finalize.
+ * @param proof Additional proof material (in the form of an array of signed states) which completes the support proof.
+ * @param candidate A candidate state (along with signatures) which is being claimed to be supported.
+ */
+ event ChallengeRegistered(
+ bytes32 indexed channelId,
+ uint48 finalizesAt,
+ SignedVariablePart[] proof,
+ SignedVariablePart candidate
+ );
+
+ /**
+ * @dev Indicates that a challenge, previously registered against `channelId`, has been cleared.
+ * @param channelId Unique identifier for a state channel.
+ * @param newTurnNumRecord A turnNum that (the adjudicator knows) is supported by a signature from each participant.
+ */
+ event ChallengeCleared(bytes32 indexed channelId, uint48 newTurnNumRecord);
+
+ /**
+ * @dev Indicates that an on-chain channel data was successfully updated and now has `newTurnNumRecord` as the latest turn number.
+ * @param channelId Unique identifier for a state channel.
+ * @param newTurnNumRecord A latest turnNum that (the adjudicator knows) is supported by adhering to channel application rules.
+ */
+ event Checkpointed(bytes32 indexed channelId, uint48 newTurnNumRecord);
+
+ /**
+ * @dev Indicates that a challenge has been registered against `channelId`.
+ * @param channelId Unique identifier for a state channel.
+ * @param finalizesAt The unix timestamp when `channelId` finalized.
+ */
+ event Concluded(bytes32 indexed channelId, uint48 finalizesAt);
+}
+```
+
+### Workflows
+
+1. **Opening a Channel**: Participants agree on initial states and open a channel on-chain.
+2. **Funding a Channel**: Participants transfer the agreed amount of funds.
+3. **Off-Chain Interaction**: Participants exchange signed state updates off-chain.
+4. **Dispute Resolution**: If a disagreement arises, a participant can force a state on-chain.
+5. **Finalization**: Upon agreement or after a timeout, the channel is finalized, and the outcome is settled.
+
+#### Example Application
+
+The CountingApp contract complies with the ForceMoveApp interface and strict turn taking logic and allows only for a simple counter to be incremented.
+
+```solidity
+contract CountingApp is IForceMoveApp {
+ struct CountingAppData {
+ uint256 counter;
+ }
+
+ /**
+ * @notice Decodes the appData.
+ * @dev Decodes the appData.
+ * @param appDataBytes The abi.encode of a CountingAppData struct describing the application-specific data.
+ * @return A CountingAppData struct containing the application-specific data.
+ */
+ function appData(bytes memory appDataBytes) internal pure returns (CountingAppData memory) {
+ return abi.decode(appDataBytes, (CountingAppData));
+ }
+
+ /**
+ * @notice Encodes application-specific rules for a particular ForceMove-compliant state channel.
+ * @dev Encodes application-specific rules for a particular ForceMove-compliant state channel.
+ * @param fixedPart Fixed part of the state channel.
+ * @param proof Array of recovered variable parts which constitutes a support proof for the candidate.
+ * @param candidate Recovered variable part the proof was supplied for.
+ */
+ function stateIsSupported(
+ FixedPart calldata fixedPart,
+ RecoveredVariablePart[] calldata proof,
+ RecoveredVariablePart calldata candidate
+ ) external pure override returns (bool, string memory) {
+ StrictTurnTaking.requireValidTurnTaking(fixedPart, proof, candidate);
+
+ require(proof.length != 0, '|proof| = 0');
+
+ // validate the proof
+ for (uint256 i = 1; i < proof.length; i++) {
+ _requireIncrementedCounter(proof[i], proof[i - 1]);
+ _requireEqualOutcomes(proof[i], proof[i - 1]);
+ }
+
+ _requireIncrementedCounter(candidate, proof[proof.length - 1]);
+ _requireEqualOutcomes(candidate, proof[proof.length - 1]);
+
+ return (true, '');
+ }
+
+ /**
+ * @notice Checks that counter encoded in first variable part equals an incremented counter in second variable part.
+ * @dev Checks that counter encoded in first variable part equals an incremented counter in second variable part.
+ * @param b RecoveredVariablePart with incremented counter.
+ * @param a RecoveredVariablePart with counter before incrementing.
+ */
+ function _requireIncrementedCounter(
+ RecoveredVariablePart memory b,
+ RecoveredVariablePart memory a
+ ) internal pure {
+ require(
+ appData(b.variablePart.appData).counter == appData(a.variablePart.appData).counter + 1,
+ 'Counter must be incremented'
+ );
+ }
+
+ /**
+ * @notice Checks that supplied signed variable parts contain the same outcome.
+ * @dev Checks that supplied signed variable parts contain the same outcome.
+ * @param a First RecoveredVariablePart.
+ * @param b Second RecoveredVariablePart.
+ */
+ function _requireEqualOutcomes(
+ RecoveredVariablePart memory a,
+ RecoveredVariablePart memory b
+ ) internal pure {
+ require(
+ Outcome.exitsEqual(a.variablePart.outcome, b.variablePart.outcome),
+ 'Outcome must not change'
+ );
+ }
+}
+```
+
+## Rationale
+
+State channels offer significant scalability improvements by minimizing on-chain transactions. This standard provides clear interfaces to ensure interoperability and efficiency while retaining flexibility for custom protocol rules.
+
+**Scalability**: State channels alleviate these concerns by moving most interactions off-chain, while the blockchain serves as a settlement layer. This construction enable high frequency applications.
+
+**Modular**: Interfaces act as contracts that specify what functions must be implemented without dictating how, allowing developers to create custom implementations suited to specific use cases while maintaining compatibility with the framework.
+
+**Interoperability**: Enable cross-chain interactions, participants can be using two differents chains for opening their channels and perform for example cross-chain atomic swaps.
+
+**Security**: The standard would enable to build an audited framework of primitive which is flexible to accommodate a large number of use cases. ERC-7824 is designed to accommodate this diversity by normalizing common protocol patterns.
+
+## Backwards Compatibility
+
+No backward compatibility issues found. This ERC is designed to coexist with existing standards and can integrate with [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) and [ERC-4337](https://www.erc4337.io/)
+
+## Security Considerations
+
+This ERC is agnostic of the protocol rules that must be implemented using IForceMoveApp. While the smart-contract framework is a simple set of convention, much of the security risk is moved off-chain. Protocols using State channels must perform security audit of their client and server backend implementation.
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://github.com/ethereum/ERCs/blob/master/LICENSE.md).
diff --git a/erc7824-docs/docs/nitrolite_client/advanced/abstract-accounts.md b/erc7824-docs/docs/nitrolite_client/advanced/abstract-accounts.md
new file mode 100644
index 000000000..ce7b5e4c5
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/advanced/abstract-accounts.md
@@ -0,0 +1,427 @@
+---
+sidebar_position: 1
+title: Abstract Accounts
+description: Using NitroliteClient with Account Abstraction
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, account abstraction, ERC-4337]
+---
+
+import MethodDetails from '@site/src/components/MethodDetails';
+import { Card, CardGrid } from '@site/src/components/Card';
+
+# Using with Abstract Accounts
+
+The `NitroliteClient` provides special support for [ERC-4337 Account Abstraction](https://eips.ethereum.org/EIPS/eip-4337) through the `txPreparer` object. This allows dApps using smart contract wallets to prepare transactions without executing them, enabling batching and other advanced patterns.
+
+## Transaction Preparer Overview
+
+The `txPreparer` is a property of the `NitroliteClient` that provides methods for preparing transaction data without sending it to the blockchain. Each method returns one or more [`PreparedTransaction`](../types.md#preparedtransaction) objects that can be used with Account Abstraction providers.
+
+```typescript
+import { NitroliteClient } from '@erc7824/nitrolite';
+
+const client = new NitroliteClient({/* config */});
+
+// Instead of: await client.deposit(amount)
+const txs = await client.txPreparer.prepareDepositTransactions(amount);
+```
+
+## Transaction Preparation Methods
+
+These methods allow you to prepare transactions for the entire channel lifecycle without executing them.
+
+### 1. Deposit Methods
+
+`}
+ example={`// Prepare deposit transaction(s) - may include ERC20 approval
+const txs = await client.txPreparer.prepareDepositTransactions(1000000n);
+
+// For each prepared transaction
+for (const tx of txs) {
+ await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+ });
+}`}
+/>
+
+`}
+ example={`// Prepare approval transaction
+const tx = await client.txPreparer.prepareApproveTokensTransaction(2000000n);
+
+// Send through your AA provider
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data
+});`}
+/>
+
+### 2. Channel Creation Methods
+
+`}
+ example={`// Prepare channel creation transaction
+const tx = await client.txPreparer.prepareCreateChannelTransaction({
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+});
+
+// Send it through your Account Abstraction provider
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+});`}
+/>
+
+`}
+ example={`// Prepare deposit + channel creation (potentially 3 txs: approve, deposit, create)
+const txs = await client.txPreparer.prepareDepositAndCreateChannelTransactions(
+ 1000000n,
+ {
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+ }
+);
+
+// Bundle these transactions into a single UserOperation
+await aaProvider.sendUserOperation({
+ userOperations: txs.map(tx => ({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+ }))
+});`}
+/>
+
+### 3. Channel Operation Methods
+
+`}
+ example={`// Prepare checkpoint transaction
+const tx = await client.txPreparer.prepareCheckpointChannelTransaction({
+ channelId: '0x...',
+ candidateState: state
+});
+
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data
+});`}
+/>
+
+`}
+ example={`// Prepare challenge transaction
+const tx = await client.txPreparer.prepareChallengeChannelTransaction({
+ channelId: '0x...',
+ candidateState: state
+});
+
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data
+});`}
+/>
+
+`}
+ example={`// Prepare resize transaction
+const tx = await client.txPreparer.prepareResizeChannelTransaction({
+ channelId: '0x...',
+ candidateState: state
+});
+
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data
+});`}
+/>
+
+### 4. Channel Closing Methods
+
+`}
+ example={`// Prepare close channel transaction
+const tx = await client.txPreparer.prepareCloseChannelTransaction({
+ finalState: {
+ channelId: '0x...',
+ stateData: '0x...',
+ allocations: [allocation1, allocation2],
+ version: 10n,
+ serverSignature: signature
+ }
+});
+
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data
+});`}
+/>
+
+### 5. Withdrawal Methods
+
+`}
+ example={`// Prepare withdrawal transaction
+const tx = await client.txPreparer.prepareWithdrawalTransaction(500000n);
+
+await aaProvider.sendUserOperation({
+ target: tx.to,
+ data: tx.data
+});`}
+/>
+
+
+## Understanding PreparedTransaction
+
+The [`PreparedTransaction`](../types.md#preparedtransaction) type is the core data structure returned by all transaction preparation methods. It contains all the information needed to construct a transaction or UserOperation:
+
+```typescript
+type PreparedTransaction = {
+ // Target contract address
+ to: Address;
+
+ // Contract call data
+ data?: Hex;
+
+ // ETH value to send (0n for token operations)
+ value?: bigint;
+};
+```
+
+Each `PreparedTransaction` represents a single contract call that can be:
+
+1. **Executed directly** - If you're using a standard EOA wallet
+2. **Bundled into a UserOperation** - For account abstraction providers
+3. **Batched with other transactions** - For advanced use cases
+
+## Integration Examples
+
+The Nitrolite transaction preparer can be integrated with any Account Abstraction provider. Here are examples with popular AA SDKs:
+
+### With Safe Account Abstraction SDK
+
+```typescript
+import { NitroliteClient } from '@erc7824/nitrolite';
+import { AccountAbstraction } from '@safe-global/account-abstraction-kit-poc';
+
+// Initialize clients
+const client = new NitroliteClient({/* config */});
+const aaKit = new AccountAbstraction(safeProvider);
+
+// Prepare transaction
+const tx = await client.txPreparer.prepareCreateChannelTransaction({
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+});
+
+// Send through AA provider
+const safeTransaction = await aaKit.createTransaction({
+ transactions: [{
+ to: tx.to,
+ data: tx.data,
+ value: tx.value?.toString() || '0'
+ }]
+});
+
+const txResponse = await aaKit.executeTransaction(safeTransaction);
+```
+
+### With Biconomy SDK
+
+```typescript
+import { NitroliteClient } from '@erc7824/nitrolite';
+import { BiconomySmartAccountV2 } from "@biconomy/account";
+
+// Initialize clients
+const client = new NitroliteClient({/* config */});
+const smartAccount = await BiconomySmartAccountV2.create({/* config */});
+
+// Prepare transaction
+const txs = await client.txPreparer.prepareDepositAndCreateChannelTransactions(
+ 1000000n,
+ {
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+ }
+);
+
+// Build user operation
+const userOp = await smartAccount.buildUserOp(
+ txs.map(tx => ({
+ to: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+ }))
+);
+
+// Send user operation
+const userOpResponse = await smartAccount.sendUserOp(userOp);
+await userOpResponse.wait();
+```
+
+## Advanced Use Cases
+
+The transaction preparer is especially powerful when combined with advanced Account Abstraction features.
+
+### Batching Multiple Operations
+
+One of the main advantages of Account Abstraction is the ability to batch multiple operations into a single transaction:
+
+```typescript
+// Collect prepared transactions from different operations
+const preparedTxs = [];
+
+// 1. Add token approval if needed
+const allowance = await client.getTokenAllowance();
+if (allowance < totalNeeded) {
+ const approveTx = await client.txPreparer.prepareApproveTokensTransaction(totalNeeded);
+ preparedTxs.push(approveTx);
+}
+
+// 2. Add deposit
+const depositTx = await client.txPreparer.prepareDepositTransactions(amount);
+preparedTxs.push(...depositTx);
+
+// 3. Add channel creation
+const createChannelTx = await client.txPreparer.prepareCreateChannelTransaction(params);
+preparedTxs.push(createChannelTx);
+
+// 4. Execute all as a batch with your AA provider
+await aaProvider.sendUserOperation({
+ userOperations: preparedTxs.map(tx => ({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+ }))
+});
+```
+
+### Gas Sponsoring
+
+Account Abstraction enables gas sponsorship, where someone else pays for the transaction gas:
+
+```typescript
+// Prepare transaction
+const tx = await client.txPreparer.prepareCreateChannelTransaction(params);
+
+// Use a sponsored transaction
+await paymasterProvider.sponsorTransaction({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n,
+ user: userAddress
+});
+```
+
+### Session Keys
+
+Some AA wallets support session keys, which are temporary keys with limited permissions:
+
+```typescript
+// Create a session key with permissions only for specific operations
+const sessionKeyData = await aaWallet.createSessionKey({
+ permissions: [
+ {
+ target: client.addresses.custody,
+ // Only allow specific functions
+ functionSelector: [
+ "0xdeposit(...)",
+ "0xwithdraw(...)"
+ ]
+ }
+ ],
+ expirationTime: Date.now() + 3600 * 1000 // 1 hour
+});
+
+// Use the session key to prepare and send transactions
+const tx = await client.txPreparer.prepareDepositTransactions(amount);
+await aaWallet.executeWithSessionKey(sessionKeyData, {
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+});
+```
+
+## Best Practices
+
+
+
+
Batch Related Operations
+
Use prepareDepositAndCreateChannelTransactions to batch deposit and channel creation into a single user operation.
+
+
+
+
Handle Approvals
+
For ERC20 tokens, prepareDepositTransactions will include an approval transaction if needed. Always process all returned transactions.
+
+
+
+
State Signing
+
Even when using Account Abstraction, state signatures are handled separately using the stateWalletClient (or walletClient if not specified).
+
+
+
+
Error Handling
+
The preparation methods throw the same errors as their execution counterparts, so use the same error handling patterns.
+
+
+
+
Check Token Allowances
+
Before preparing token operations, you can check if approval is needed:
When using Account Abstraction, gas estimation is typically handled by the AA provider, but you can request estimates if needed.
+
+
+
+## Limitations
+
+:::caution Important
+- The transaction preparer **doesn't handle sequencing or nonce management** - that's the responsibility of your AA provider.
+- Some operations (like checkpointing) require signatures from all participants, which must be collected separately from the transaction preparation.
+:::
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_client/advanced/erc20-service.md b/erc7824-docs/docs/nitrolite_client/advanced/erc20-service.md
new file mode 100644
index 000000000..c8e3c6e23
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/advanced/erc20-service.md
@@ -0,0 +1,218 @@
+---
+sidebar_position: 3
+title: Erc20Service
+description: Documentation for the ERC20Service class
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced, erc20, tokens]
+---
+
+# Erc20Service
+
+The `Erc20Service` class provides a convenient interface for interacting with ERC20 tokens. It handles token approvals, allowance checks, and balance inquiries that are essential for deposit and withdrawal operations in the Nitrolite system.
+
+## Initialization
+
+```typescript
+import { Erc20Service } from '@erc7824/nitrolite';
+
+const erc20Service = new Erc20Service(
+ publicClient, // viem PublicClient
+ walletClient // viem WalletClient
+);
+```
+
+## Core Methods
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `approve` | Approves a spender to use tokens. | `tokenAddress: Address, spender: Address, amount: bigint` | `Promise` |
+| `getTokenAllowance` | Gets token allowance for a spender. | `tokenAddress: Address, owner: Address, spender: Address` | `Promise` |
+| `getTokenBalance` | Gets token balance for an account. | `tokenAddress: Address, account: Address` | `Promise` |
+
+## Method Details
+
+### Approve Tokens
+
+Approves a spender (typically the Custody contract) to transfer tokens on behalf of the owner.
+
+```typescript
+// Approve the custody contract to spend 1000 tokens
+const txHash = await erc20Service.approve(
+ tokenAddress, // ERC20 token address
+ spenderAddress, // Custody contract address
+ 1000000000000000000n // Amount to approve (1 token with 18 decimals)
+);
+```
+
+**Important notes:**
+- For security reasons, always specify the exact amount you want to approve
+- Consider using the ERC20 token decimals for the amount calculation
+- The transaction will fail if the owner has insufficient balance
+
+### Get Token Allowance
+
+Retrieves the current allowance granted by an owner to a spender.
+
+```typescript
+// Check current allowance
+const allowance = await erc20Service.getTokenAllowance(
+ tokenAddress, // ERC20 token address
+ ownerAddress, // Owner's address
+ spenderAddress // Spender's address (custody contract)
+);
+
+console.log(`Current allowance: ${allowance}`);
+
+// Check if allowance is sufficient
+if (allowance < requiredAmount) {
+ console.log('Need to approve more tokens');
+}
+```
+
+### Get Token Balance
+
+Retrieves the token balance for a specific account.
+
+```typescript
+// Check token balance
+const balance = await erc20Service.getTokenBalance(
+ tokenAddress, // ERC20 token address
+ accountAddress // Account to check
+);
+
+console.log(`Account balance: ${balance}`);
+
+// Check if balance is sufficient
+if (balance < requiredAmount) {
+ console.log('Insufficient token balance');
+}
+```
+
+## Transaction Preparation
+
+For Account Abstraction support, `Erc20Service` provides a transaction preparation method:
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `prepareApprove` | Prepares an approval transaction. | `tokenAddress: Address, spender: Address, amount: bigint` | `Promise` |
+
+Example:
+```typescript
+// Prepare approval transaction
+const tx = await erc20Service.prepareApprove(
+ tokenAddress,
+ spenderAddress,
+ amount
+);
+
+// Use with your Account Abstraction provider
+const userOp = await aaProvider.buildUserOperation({
+ target: tx.to,
+ data: tx.data,
+ value: 0n // ERC20 approvals don't require ETH
+});
+```
+
+## Implementation Details
+
+The `Erc20Service` uses the standard ERC20 interface methods:
+
+- `approve`: Allows a spender to withdraw tokens from the owner's account, up to the specified amount
+- `allowance`: Returns the remaining tokens that a spender is allowed to withdraw
+- `balanceOf`: Returns the token balance of the specified account
+
+## Working with Token Decimals
+
+ERC20 tokens typically have decimal places (most commonly 18). When working with token amounts, you should account for these decimals:
+
+```typescript
+import { parseUnits } from 'viem';
+
+// For a token with 18 decimals
+const tokenDecimals = 18;
+
+// Convert 1.5 tokens to the smallest unit
+const amount = parseUnits('1.5', tokenDecimals);
+
+// Approve the amount
+await erc20Service.approve(tokenAddress, spenderAddress, amount);
+```
+
+## Error Handling
+
+The `Erc20Service` throws specific error types:
+
+- `TokenError`: For token-specific errors (insufficient balance, approval failures)
+- `ContractCallError`: When calls to the contract fail
+- `WalletClientRequiredError`: When wallet client is needed but not provided
+
+Example:
+```typescript
+try {
+ await erc20Service.approve(tokenAddress, spenderAddress, amount);
+} catch (error) {
+ if (error instanceof TokenError) {
+ console.error(`Token error: ${error.message}`);
+ console.error(`Suggestion: ${error.suggestion}`);
+
+ // Check for specific token error conditions
+ if (error.details?.errorName === 'InsufficientBalance') {
+ console.log(`Available balance: ${error.details.available}`);
+ }
+ }
+}
+```
+
+## Common Patterns
+
+### Checking and Approving Tokens
+
+A common pattern is to check if the current allowance is sufficient before approving more tokens:
+
+```typescript
+// Get current allowance
+const allowance = await erc20Service.getTokenAllowance(
+ tokenAddress,
+ ownerAddress,
+ spenderAddress
+);
+
+// If allowance is insufficient, approve more tokens
+if (allowance < requiredAmount) {
+ await erc20Service.approve(tokenAddress, spenderAddress, requiredAmount);
+}
+
+// Now proceed with the operation that requires the approval
+// (e.g., deposit into custody contract)
+```
+
+### Handling Multiple Tokens
+
+If your application works with multiple tokens, you can reuse the same `Erc20Service` instance:
+
+```typescript
+// Same service instance for different tokens
+const erc20Service = new Erc20Service(publicClient, walletClient);
+
+// Work with token A
+const balanceA = await erc20Service.getTokenBalance(tokenAddressA, accountAddress);
+
+// Work with token B
+const balanceB = await erc20Service.getTokenBalance(tokenAddressB, accountAddress);
+```
+
+## Integration with NitroliteClient
+
+When using the `NitroliteClient`, you typically don't need to interact with `Erc20Service` directly, as the client handles these operations for you:
+
+```typescript
+// NitroliteClient handles token approvals automatically during deposit
+await nitroliteClient.deposit(amount);
+
+// For explicit approval without deposit
+await nitroliteClient.approveTokens(amount);
+
+// Get token balance through the client
+const balance = await nitroliteClient.getTokenBalance();
+```
+
+However, for advanced use cases or custom token interaction, direct access to `Erc20Service` can be useful.
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_client/advanced/index.md b/erc7824-docs/docs/nitrolite_client/advanced/index.md
new file mode 100644
index 000000000..01f03d640
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/advanced/index.md
@@ -0,0 +1,150 @@
+---
+sidebar_position: 3
+title: Advanced Usage
+description: Advanced topics for working with the NitroliteClient
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced]
+---
+
+# Advanced Usage
+
+This section covers advanced topics for working with the `@erc7824/nitrolite` SDK. These are intended for developers who need deeper control over the state channel operations or want to integrate with specialized systems.
+
+## Available Topics
+
+### Low-level Services
+
+The NitroliteClient is built on top of specialized services that can be used directly for more fine-grained control:
+
+- [NitroliteService](./nitrolite-service.md) - Core service for interacting with the custody contract and managing channels
+- [Erc20Service](./erc20-service.md) - Service for interacting with ERC20 tokens
+
+### Account Abstraction Integration
+
+- [Abstract Accounts](./abstract-accounts.md) - Using NitroliteClient with ERC-4337 Account Abstraction for smart contract wallets
+
+## When to Use Advanced Features
+
+Consider using the advanced features when:
+
+1. **Building custom workflows** that require more control than the high-level NitroliteClient API provides
+2. **Integrating with smart contract wallets** or other account abstraction systems
+3. **Implementing specialized monitoring or management systems** for state channels
+4. **Developing cross-chain applications** that require custom handling of state channel operations
+5. **Optimizing gas usage** through transaction batching and other techniques
+
+## Example: Direct Service Usage
+
+While using the high-level NitroliteClient is recommended for most applications, here's how you can work directly with the services:
+
+```typescript
+import {
+ NitroliteService,
+ Erc20Service,
+ getChannelId,
+ signState
+} from '@erc7824/nitrolite';
+
+// Initialize services
+const nitroliteService = new NitroliteService(
+ publicClient,
+ addresses,
+ walletClient,
+ account.address
+);
+
+const erc20Service = new Erc20Service(
+ publicClient,
+ walletClient
+);
+
+// Check token allowance
+const allowance = await erc20Service.getTokenAllowance(
+ tokenAddress,
+ account.address,
+ addresses.custody
+);
+
+// Approve tokens if needed
+if (allowance < depositAmount) {
+ await erc20Service.approve(tokenAddress, addresses.custody, depositAmount);
+}
+
+// Deposit funds
+await nitroliteService.deposit(tokenAddress, depositAmount);
+
+// Create channel with custom parameters
+const channelNonce = generateChannelNonce(account.address);
+const channel = {
+ participants: [account.address, counterpartyAddress],
+ adjudicator: addresses.adjudicator,
+ challenge: 100n, // Challenge duration in seconds
+ nonce: channelNonce
+};
+
+// Prepare initial state
+const initialState = {
+ intent: StateIntent.INITIALIZE,
+ version: 0n,
+ data: '0x1234', // Application-specific data
+ allocations: [
+ { destination: account.address, token: tokenAddress, amount: 700000n },
+ { destination: counterpartyAddress, token: tokenAddress, amount: 300000n }
+ ],
+ sigs: [] // Will be filled with signatures
+};
+
+// Sign the state
+const channelId = getChannelId(channel);
+const stateHash = getStateHash(channelId, initialState);
+const signature = await signState(walletClient, stateHash);
+initialState.sigs = [signature];
+
+// Create the channel
+await nitroliteService.createChannel(channel, initialState);
+```
+
+## Example: Complex Transaction Preparation
+
+For applications using Account Abstraction, you can prepare complex transaction sequences:
+
+```typescript
+import { NitroliteClient, NitroliteTransactionPreparer } from '@erc7824/nitrolite';
+
+// Initialize client
+const client = new NitroliteClient({/* config */});
+
+// Access the transaction preparer directly
+const txPreparer = client.txPreparer;
+
+// Prepare a complete sequence (approve, deposit, create channel)
+const allTxs = [];
+
+// 1. Check and prepare token approval if needed
+const allowance = await client.getTokenAllowance();
+if (allowance < depositAmount) {
+ const approveTx = await txPreparer.prepareApproveTokensTransaction(depositAmount);
+ allTxs.push(approveTx);
+}
+
+// 2. Prepare deposit
+const depositTx = await txPreparer.prepareDepositTransactions(depositAmount);
+allTxs.push(...depositTx);
+
+// 3. Prepare channel creation
+const createChannelTx = await txPreparer.prepareCreateChannelTransaction({
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+});
+allTxs.push(createChannelTx);
+
+// 4. Use with your Account Abstraction provider
+await aaProvider.sendUserOperation({
+ userOperations: allTxs.map(tx => ({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+ }))
+});
+```
+
+These advanced techniques give you greater flexibility and control over the state channel operations, but they also require a deeper understanding of the underlying protocol.
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_client/advanced/nitrolite-service.md b/erc7824-docs/docs/nitrolite_client/advanced/nitrolite-service.md
new file mode 100644
index 000000000..c4452fc41
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/advanced/nitrolite-service.md
@@ -0,0 +1,202 @@
+---
+sidebar_position: 2
+title: NitroliteService
+description: Documentation for the core NitroliteService class
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced]
+---
+
+# NitroliteService
+
+The `NitroliteService` class is the core service that directly interacts with the Nitrolite Custody smart contract. It handles channel management, deposits, withdrawals, and all other channel-specific operations following the channel lifecycle.
+
+## Initialization
+
+```typescript
+import { NitroliteService } from '@erc7824/nitrolite';
+
+const nitroliteService = new NitroliteService(
+ publicClient, // viem PublicClient
+ addresses, // ContractAddresses
+ walletClient, // viem WalletClient
+ account // Account address
+);
+```
+
+## Channel Lifecycle Methods
+
+### 1. Deposit Operations
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `deposit` | Deposits tokens/ETH into the custody contract. | `tokenAddress: Address, amount: bigint` | `Promise` |
+| `withdraw` | Withdraws tokens from the custody contract. | `tokenAddress: Address, amount: bigint` | `Promise` |
+
+Example:
+```typescript
+// Deposit ETH or token
+const txHash = await nitroliteService.deposit(tokenAddress, amount);
+
+// Withdraw ETH or token
+const txHash = await nitroliteService.withdraw(tokenAddress, amount);
+```
+
+### 2. Channel Creation
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `createChannel` | Creates a new channel with the given parameters. | `channel: Channel, initialState: State` | `Promise` |
+
+Example:
+```typescript
+// Create a channel
+const txHash = await nitroliteService.createChannel(channel, initialState);
+```
+
+Where:
+- `channel` defines the participants, adjudicator, challenge period, and nonce
+- `initialState` contains the initial allocation of funds and state data
+
+### 3. Channel Operations
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `checkpoint` | Checkpoints a state on-chain. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` |
+| `challenge` | Challenges a channel with a candidate state. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` |
+| `resize` | Resizes a channel with a candidate state. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` |
+
+Example:
+```typescript
+// Checkpoint a channel state
+const txHash = await nitroliteService.checkpoint(channelId, candidateState);
+
+// Challenge a channel
+const txHash = await nitroliteService.challenge(channelId, candidateState);
+
+// Resize a channel
+const txHash = await nitroliteService.resize(channelId, candidateState);
+```
+
+### 4. Channel Closing
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `close` | Closes a channel using a final state. | `channelId: ChannelId, finalState: State` | `Promise` |
+
+Example:
+```typescript
+// Close a channel
+const txHash = await nitroliteService.close(channelId, finalState);
+```
+
+### 5. Account Information
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `getAccountChannels` | Gets channel IDs for an account. | `accountAddress: Address` | `Promise` |
+| `getAccountInfo` | Gets account info for a token. | `accountAddress: Address, tokenAddress: Address` | `Promise` |
+
+Example:
+```typescript
+// Get all channels for an account
+const channels = await nitroliteService.getAccountChannels(accountAddress);
+
+// Get detailed account info
+const info = await nitroliteService.getAccountInfo(accountAddress, tokenAddress);
+console.log(`Available: ${info.available}, Locked: ${info.locked}`);
+```
+
+## Transaction Preparation Methods
+
+For Account Abstraction support, NitroliteService provides transaction preparation methods that return transaction data without executing it:
+
+| Method | Description | Parameters | Return Type |
+|--------|-------------|------------|------------|
+| `prepareDeposit` | Prepares deposit transaction. | `tokenAddress: Address, amount: bigint` | `Promise` |
+| `prepareCreateChannel` | Prepares channel creation transaction. | `channel: Channel, initialState: State` | `Promise` |
+| `prepareCheckpoint` | Prepares checkpoint transaction. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` |
+| `prepareChallenge` | Prepares challenge transaction. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` |
+| `prepareResize` | Prepares resize transaction. | `channelId: ChannelId, candidateState: State, proofStates?: State[]` | `Promise` |
+| `prepareClose` | Prepares close transaction. | `channelId: ChannelId, finalState: State` | `Promise` |
+| `prepareWithdraw` | Prepares withdraw transaction. | `tokenAddress: Address, amount: bigint` | `Promise` |
+
+Example:
+```typescript
+// Prepare deposit transaction
+const tx = await nitroliteService.prepareDeposit(tokenAddress, amount);
+
+// Use with your Account Abstraction provider
+const userOp = await aaProvider.buildUserOperation({
+ target: tx.to,
+ data: tx.data,
+ value: tx.value || 0n
+});
+```
+
+## Implementation Details
+
+The `NitroliteService` connects to the Custody contract using:
+
+- A viem `PublicClient` for read operations
+- A viem `WalletClient` for write operations and signing
+- The contract address specified in the configuration
+
+The service handles:
+- Contract interaction
+- Parameter validation
+- Error handling
+- Transaction preparation
+
+## Error Handling
+
+The `NitroliteService` throws specific error types:
+
+- `ContractCallError`: When calls to the contract fail
+- `InvalidParameterError`: When parameters are invalid
+- `MissingParameterError`: When required parameters are missing
+- `WalletClientRequiredError`: When wallet client is needed but not provided
+- `AccountRequiredError`: When account is needed but not provided
+
+Example:
+```typescript
+try {
+ await nitroliteService.deposit(tokenAddress, amount);
+} catch (error) {
+ if (error instanceof ContractCallError) {
+ console.error(`Contract call failed: ${error.message}`);
+ console.error(`Suggestion: ${error.suggestion}`);
+ }
+}
+```
+
+## Advanced Usage
+
+### Custom Contract Interaction
+
+For advanced use cases, you might need to interact directly with the contract:
+
+```typescript
+// Get the custody contract address
+const custodyAddress = nitroliteService.custodyAddress;
+
+// Use with your own custom contract interaction
+const customContract = getContract({
+ address: custodyAddress,
+ abi: custodyAbi,
+ // Additional configuration...
+});
+```
+
+### Multiple Channel Management
+
+For applications managing multiple channels:
+
+```typescript
+// Get all channels for the account
+const channels = await nitroliteService.getAccountChannels(accountAddress);
+
+// Process each channel
+for (const channelId of channels) {
+ // Get channel info from contract
+ // Process channel state
+}
+```
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_client/advanced/supported-sig-formats.md b/erc7824-docs/docs/nitrolite_client/advanced/supported-sig-formats.md
new file mode 100644
index 000000000..9422f138e
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/advanced/supported-sig-formats.md
@@ -0,0 +1,86 @@
+---
+sidebar_position: 4
+title: Supported Signature Formats
+description: Documentation for supported signature formats in NitroliteClient
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, advanced, signature, format, ECDSA, EIP-191, EIP-712, EIP-1271, EIP-6492]
+---
+
+# Supported Signature Formats
+
+The nitrolite smart contract supports multiple signature formats over a State to accommodate various use cases and compatibility with different wallets and applications.
+
+The message being signed is a channelId and State, formatted in a specific way. The most common is a `packedState`, which is calculated as follows:
+
+```solidity
+abi.encode(channelId, state.intent, state.version, state.data, state.allocations)
+```
+
+## EOA signatures
+
+Externally Owned Accounts (EOAs) can sign messages with their private key using the ECDSA.
+
+Based on how the message is handled before signing, the following formats are supported:
+
+### Raw ECDSA Signature
+
+The message is a `packedState`, that is hashed with `keccak256` before signing. The signature is a 65-byte ECDSA signature.
+
+### EIP-191 Signature
+
+You can read more about EIP-191 in the [EIP-191 specification](https://eips.ethereum.org/EIPS/eip-191).
+
+The message is a `packedState` prefixed with `"\x19Ethereum Signed Message:\n" + len(packedState)` and hashed with `keccak256` before signing. The signature is a 65-byte ECDSA signature.
+
+### EIP-712 Signature
+
+You can read more about EIP-712 in the [EIP-712 specification](https://eips.ethereum.org/EIPS/eip-712).
+
+The message is an `AllowStateHash` typed data, calculated as follows:
+
+```solidity
+abi.encode(
+ typeHash,
+ channelId,
+ state.intent,
+ state.version,
+ keccak256(state.data),
+ keccak256(abi.encode(state.allocations))
+);
+```
+
+Where `typeHash` is `AllowStateHash(bytes32 channelId,uint8 intent,uint256 version,bytes data,Allocation[] allocations)Allocation(address destination,address token,uint256 amount)`.
+
+The message is then hashed with `keccak256`, appended to `"\x19\x01" || domainSeparator` and signed. The signature is a 65-byte ECDSA signature.
+
+`||` is a concatenation operator, and `domainSeparator` is calculated as follows:
+
+```solidity
+keccak256(
+ abi.encode(
+ EIP712_TYPE_HASH,
+ keccak256(name),
+ keccak256(version),
+ chainId,
+ verifyingContract
+ )
+);
+```
+
+`EIP712_TYPE_HASH` is `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
+
+Additionally, `name`, `version` are the name and version of the Custody contract, `chainId` is the chain ID of the network, and `verifyingContract` is the address of the contract.
+
+## Smart Contract Signatures
+
+Smart Contracts that support [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271) or [EIP-6492](https://eips.ethereum.org/EIPS/eip-6492) can sign messages using their own logic. When checking such signatures, the nitrolite smart contract will pass the `keccak256` hash of the `packedState` as a message hash for verification.
+
+See the aforementioned EIP standards for details on how these signatures are structured and verified. If you want to add support for such signatures in your client, you probably need to look at how signature verification logic is implemented in the Smart Contract (Smart Wallet, etc) that will use them.
+
+## Challenge Signatures
+
+The aforementioned signature formats are used to sign States, however to submit a challenge, the user must provide a `challengerSignature`, which proves that the user has the right to challenge a Channel.
+
+Depending on a signature format, the `challengerSignature` is calculated differently from the common State signature:
+
+- **Raw ECDSA, EIP-191, EIP-1271 and EIP-6492**: The message (`packedState`) is suffixed with a `challenge` string (`abi.encodePacked(packedState, "challenge")`).
+- **EIP-712**: The `typeHash` name is `AllowChallengeStateHash`, while type format remains the same.
diff --git a/erc7824-docs/docs/nitrolite_client/index.mdx b/erc7824-docs/docs/nitrolite_client/index.mdx
new file mode 100644
index 000000000..0ddb5914f
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/index.mdx
@@ -0,0 +1,122 @@
+---
+sidebar_position: 2
+title: NitroliteClient
+description: Nitrolite client SDK documentation
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, react tutorial]
+---
+
+import { Card, CardGrid } from '@site/src/components/Card';
+
+# NitroliteClient
+
+The `NitroliteClient` class is the main entry point for interacting with Nitrolite state channels. It provides a comprehensive set of methods for managing channels, deposits, and funds.
+
+
+
+
+
+
+
+
+## Quick Start
+
+```typescript
+import { NitroliteClient } from '@erc7824/nitrolite';
+
+// Initialize client
+const nitroliteClient = new NitroliteClient({
+ publicClient,
+ walletClient,
+ addresses: {
+ custody: '0x...',
+ adjudicator: '0x...',
+ guestAddress: '0x...',
+ tokenAddress: '0x...'
+ },
+ chainId: 137, // Polygon mainnet
+ challengeDuration: 100n
+});
+
+// 1. Deposit funds
+const depositTxHash = await nitroliteClient.deposit(1000000n);
+
+// 2. Create a channel
+const { channelId, initialState, txHash } = await nitroliteClient.createChannel({
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+});
+
+// 3. Resize the channel when needed
+const resizeTxHash = await nitroliteClient.resizeChannel({
+ resizeState: {
+ channelId,
+ stateData: '0x5678',
+ allocations: newAllocations,
+ version: 2n,
+ intent: StateIntent.RESIZE,
+ serverSignature: signature
+ },
+ proofStates: []
+});
+
+// 4. Close the channel
+const closeTxHash = await nitroliteClient.closeChannel({
+ finalState: {
+ channelId,
+ stateData: '0x5678',
+ allocations: newAllocations,
+ version: 5n,
+ serverSignature: signature
+ }
+});
+
+// 5. Withdraw funds
+const withdrawTxHash = await nitroliteClient.withdrawal(800000n);
+```
+
+{/* ## Learning Path
+
+
+
+
+
+
+
+
+ */}
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_client/methods.mdx b/erc7824-docs/docs/nitrolite_client/methods.mdx
new file mode 100644
index 000000000..2cd7f3973
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/methods.mdx
@@ -0,0 +1,236 @@
+---
+sidebar_position: 1
+title: Methods
+description: Complete API reference for NitroliteClient
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, react tutorial]
+---
+
+import MethodDetails from '@site/src/components/MethodDetails';
+import { Card, CardGrid } from '@site/src/components/Card';
+
+# Methods
+
+This page provides a complete reference for all methods available in the `NitroliteClient` class from the `@erc7824/nitrolite` package.
+
+## Channel Lifecycle Methods
+
+These methods are organized according to the typical lifecycle of a state channel.
+
+### 1. Deposit Methods
+
+The deposit phase includes methods for managing funds in the custody contract and handling token approvals.
+
+
+
+
+
+
+
+
+
+### 2. Channel Creation Methods
+
+
+
+
+
+### 3. Channel Operation Methods
+
+
+
+
+
+
+
+### 4. Channel Closing Methods
+
+
+
+### 5. Withdrawal Methods
+
+
+
+## Account Information Methods
+
+These methods provide information about your account's state:
+
+
+
+
+
+:::caution Advanced Usage: Transaction Preparation
+For Account Abstraction support and transaction preparation methods, see the [Abstract Accounts](./advanced/abstract-accounts) page.
+:::
+
+## Example: Complete Channel Lifecycle
+
+```typescript
+import { NitroliteClient } from '@erc7824/nitrolite';
+
+// Initialize the client
+const client = new NitroliteClient({
+ publicClient,
+ walletClient,
+ addresses: { custody, adjudicator, guestAddress, tokenAddress },
+ chainId: 137,
+ challengeDuration: 100n
+});
+
+// 1. Deposit funds
+const depositTxHash = await client.deposit(1000000n);
+
+// 2. Create a channel
+const { channelId, initialState } = await client.createChannel({
+ initialAllocationAmounts: [700000n, 300000n],
+ stateData: '0x1234'
+});
+
+// 3. Get account info to verify funds are locked
+const accountInfo = await client.getAccountInfo();
+console.log(`Locked in channels: ${accountInfo.locked}`);
+
+// 4. Later, resize the channel
+const resizeTxHash = await client.resizeChannel({
+ resizeState: {
+ channelId,
+ stateData: '0x5678',
+ allocations: newAllocations,
+ version: 2n,
+ intent: StateIntent.RESIZE,
+ serverSignature: signature
+ },
+ proofStates: []
+});
+
+// 5. Close the channel
+const closeTxHash = await client.closeChannel({
+ finalState: {
+ channelId,
+ stateData: '0x5678',
+ allocations: [
+ { destination: userAddress, token: tokenAddress, amount: 800000n },
+ { destination: counterpartyAddress, token: tokenAddress, amount: 200000n }
+ ],
+ version: 5n,
+ serverSignature: signature
+ }
+});
+
+// 6. Withdraw funds
+const withdrawTxHash = await client.withdrawal(800000n);
+```
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_client/types.md b/erc7824-docs/docs/nitrolite_client/types.md
new file mode 100644
index 000000000..e82cec173
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_client/types.md
@@ -0,0 +1,371 @@
+---
+sidebar_position: 2
+title: Type Definitions
+description: Detailed type definitions used in the NitroliteClient
+keywords: [erc7824, statechannels, state channels, nitrolite, ethereum scaling, layer 2, off-chain, react tutorial]
+---
+
+# Type Definitions
+
+This page documents the core types used throughout the `@erc7824/nitrolite` SDK. Understanding these types is essential for effectively working with the `NitroliteClient`.
+
+## Core Types
+
+### ChannelId
+
+```typescript
+type ChannelId = Hex;
+```
+
+A unique identifier for a state channel, represented as a hexadecimal string.
+
+### StateHash
+
+```typescript
+type StateHash = Hex;
+```
+
+A hash of a channel state, represented as a hexadecimal string.
+
+### Signature
+
+```typescript
+type Signature = Hex;
+```
+
+Represents a cryptographic signature used for signing state channel states as a hexadecimal string.
+
+### Allocation
+
+```typescript
+interface Allocation {
+ destination: Address; // Where funds are sent on channel closure
+ token: Address; // ERC-20 token address (zero address for ETH)
+ amount: bigint; // Token amount allocated
+}
+```
+
+Represents the allocation of funds to a particular destination.
+
+### Channel
+
+```typescript
+interface Channel {
+ participants: [Address, Address]; // List of participants [Host, Guest]
+ adjudicator: Address; // Address of the contract that validates states
+ challenge: bigint; // Duration in seconds for challenge period
+ nonce: bigint; // Unique per channel with same participants and adjudicator
+}
+```
+
+Represents the core configuration of a state channel.
+
+### StateIntent
+
+```typescript
+enum StateIntent {
+ OPERATE, // Operate the state application
+ INITIALIZE, // Initial funding state
+ RESIZE, // Resize state
+ FINALIZE, // Final closing state
+}
+```
+
+Indicates the intent of a state update. The intent determines how the state is processed by the channel participants and the blockchain.
+
+### State
+
+```typescript
+interface State {
+ intent: StateIntent; // Intent of the state
+ version: bigint; // Version number, incremented for each update
+ data: Hex; // Application data encoded as hex
+ allocations: [Allocation, Allocation]; // Asset allocation for each participant
+ sigs: Signature[]; // State hash signatures
+}
+```
+
+Represents a complete state channel state, including allocations and signatures.
+
+## Configuration Types
+
+### NitroliteClientConfig
+
+```typescript
+interface NitroliteClientConfig {
+ /** The viem PublicClient for reading blockchain data. */
+ publicClient: PublicClient;
+
+ /**
+ * The viem WalletClient used for:
+ * 1. Sending on-chain transactions in direct execution methods (e.g., `client.deposit`).
+ * 2. Providing the 'account' context for transaction preparation (`client.txPreparer`).
+ * 3. Signing off-chain states *if* `stateWalletClient` is not provided.
+ */
+ walletClient: WalletClient>;
+
+ /**
+ * Optional: A separate viem WalletClient used *only* for signing off-chain state updates (`signMessage`).
+ * Provide this if you want to use a different key (e.g., a "hot" key from localStorage)
+ * for state signing than the one used for on-chain transactions.
+ * If omitted, `walletClient` will be used for state signing.
+ */
+ stateWalletClient?: WalletClient>;
+
+ /** Contract addresses required by the SDK. */
+ addresses: ContractAddresses;
+
+ /** Chain ID for the channel */
+ chainId: number;
+
+ /** Optional: Default challenge duration (in seconds) for new channels. Defaults to 0 if omitted. */
+ challengeDuration?: bigint;
+}
+```
+
+Configuration for initializing the `NitroliteClient`.
+
+### ContractAddresses
+
+```typescript
+interface ContractAddresses {
+ custody: Address; // Custody contract address
+ adjudicator: Address; // Adjudicator contract address
+ guestAddress: Address; // Guest participant address
+ tokenAddress: Address; // Token address (zero address for ETH)
+}
+```
+
+Addresses of contracts used by the Nitrolite SDK.
+
+## Channel Lifecycle Parameter Types
+
+### 1. Deposit Phase
+
+Deposit operations primarily use simple `bigint` parameters for amounts.
+
+### 2. Channel Creation
+
+```typescript
+interface CreateChannelParams {
+ initialAllocationAmounts: [bigint, bigint]; // Initial allocation for [host, guest]
+ stateData?: Hex; // Application-specific data
+}
+```
+
+Parameters for creating a new channel.
+
+### 3. Channel Operations
+
+```typescript
+interface CheckpointChannelParams {
+ channelId: ChannelId; // Channel ID to checkpoint
+ candidateState: State; // State to checkpoint
+ proofStates?: State[]; // Optional proof states
+}
+
+interface ChallengeChannelParams {
+ channelId: ChannelId; // Channel ID to challenge
+ candidateState: State; // State to submit as a challenge
+ proofStates?: State[]; // Optional proof states
+}
+
+interface ResizeChannelParams {
+ resizeState: {
+ channelId: ChannelId;
+ stateData: Hex;
+ allocations: [Allocation, Allocation];
+ version: bigint;
+ intent: StateIntent;
+ serverSignature: Signature;
+ };
+ proofStates: State[];
+}
+```
+
+Parameters for channel operations.
+
+### 4. Channel Closing
+
+```typescript
+interface CloseChannelParams {
+ stateData?: Hex; // Optional application data for the final state
+ finalState: {
+ channelId: ChannelId; // Channel ID to close
+ stateData: Hex; // Application-specific data
+ allocations: [Allocation, Allocation]; // Final allocations
+ version: bigint; // State version
+ serverSignature: Signature; // Server's signature on the state
+ };
+}
+```
+
+Parameters for collaboratively closing a channel.
+
+## Return Types
+
+### AccountInfo
+
+```typescript
+interface AccountInfo {
+ available: bigint; // Available funds in the custody contract
+ channelCount: bigint; // Number of channels
+}
+```
+
+Information about an account's funds in the custody contract.
+
+### PreparedTransaction
+
+```typescript
+type PreparedTransaction = {
+ to: Address; // Target contract address
+ data?: Hex; // Contract call data
+ value?: bigint; // ETH value to send
+};
+```
+
+Represents the data needed to construct a transaction, used by the transaction preparer for Account Abstraction.
+
+## Type Usage By Channel Lifecycle Phase
+
+### 1. Deposit Phase
+
+```typescript
+// Deposit
+await client.deposit(amount: bigint): Promise
+
+// Check token details
+const balance: bigint = await client.getTokenBalance()
+const allowance: bigint = await client.getTokenAllowance()
+```
+
+### 2. Channel Creation Phase
+
+```typescript
+// Create channel
+const result: {
+ channelId: ChannelId;
+ initialState: State;
+ txHash: Hash
+} = await client.createChannel({
+ initialAllocationAmounts: [bigint, bigint],
+ stateData: Hex
+})
+```
+
+### 3. Channel Operation Phase
+
+```typescript
+// Resize
+await client.resizeChannel({
+ resizeState: {
+ channelId: ChannelId,
+ stateData: Hex,
+ allocations: [Allocation, Allocation],
+ version: bigint,
+ intent: StateIntent,
+ serverSignature: Signature
+ },
+ proofStates: State[]
+}): Promise
+
+// Resize is structured as:
+const resizeParams: ResizeChannelParams = {
+ resizeState: {
+ channelId,
+ stateData: '0x1234',
+ allocations: [
+ { destination: addr1, token: tokenAddr, amount: 600000n },
+ { destination: addr2, token: tokenAddr, amount: 400000n }
+ ],
+ version: 2n, // Incremented from previous
+ intent: StateIntent.RESIZE,
+ serverSignature: signature
+ },
+ proofStates: []
+}
+```
+
+### 4. Channel Closing Phase
+
+```typescript
+// Close
+await client.closeChannel({
+ finalState: {
+ channelId: ChannelId,
+ stateData: Hex,
+ allocations: [Allocation, Allocation],
+ version: bigint,
+ serverSignature: Signature
+ }
+}): Promise
+```
+
+### 5. Withdrawal Phase
+
+```typescript
+// Withdraw
+await client.withdrawal(amount: bigint): Promise
+```
+
+## State Intent Lifecycle
+
+The `StateIntent` enum value determines how a state is interpreted:
+
+1. `StateIntent.INITIALIZE`: Used when creating a channel, defines the initial funding allocations
+2. `StateIntent.OPERATE`: Used during normal operations, for application-specific state updates
+3. `StateIntent.RESIZE`: Used when changing allocation amounts, e.g., adding funds to a channel
+4. `StateIntent.FINALIZE`: Used when closing a channel, defines the final allocations
+
+Example of state progression:
+
+```typescript
+// 1. Initial state (on channel creation)
+const initialState = {
+ intent: StateIntent.INITIALIZE,
+ version: 0n,
+ data: '0x1234',
+ allocations: [
+ { destination: userAddr, token: tokenAddr, amount: 700000n },
+ { destination: guestAddr, token: tokenAddr, amount: 300000n }
+ ],
+ sigs: [userSig, guestSig]
+};
+
+// 2. Operation state (during application usage)
+const operationalState = {
+ intent: StateIntent.OPERATE,
+ version: 1n, // Incremented
+ data: '0x5678', // Application data changed
+ allocations: [
+ { destination: userAddr, token: tokenAddr, amount: 650000n }, // Balance changed
+ { destination: guestAddr, token: tokenAddr, amount: 350000n } // Balance changed
+ ],
+ sigs: [userSig, guestSig]
+};
+
+// 3. Resize state (adding funds)
+const resizeState = {
+ intent: StateIntent.RESIZE,
+ version: 2n,
+ data: '0x5678',
+ allocations: [
+ { destination: userAddr, token: tokenAddr, amount: 950000n }, // Added funds
+ { destination: guestAddr, token: tokenAddr, amount: 450000n } // Added funds
+ ],
+ sigs: [userSig, guestSig]
+};
+
+// 4. Final state (closing channel)
+const finalState = {
+ intent: StateIntent.FINALIZE,
+ version: 3n,
+ data: '0x9ABC',
+ allocations: [
+ { destination: userAddr, token: tokenAddr, amount: 930000n },
+ { destination: guestAddr, token: tokenAddr, amount: 470000n }
+ ],
+ sigs: [userSig, guestSig]
+};
+```
diff --git a/erc7824-docs/docs/nitrolite_rpc/index.md b/erc7824-docs/docs/nitrolite_rpc/index.md
new file mode 100644
index 000000000..75d0d1f7d
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_rpc/index.md
@@ -0,0 +1,177 @@
+---
+sidebar_position: 3
+title: NitroliteRPC
+description: Overview of the NitroliteRPC, its core logic, and links to detailed API and type definitions.
+keywords: [erc7824, statechannels, state channels, nitrolite, rpc, websockets, messaging, protocol]
+---
+
+import { Card, CardGrid } from '@site/src/components/Card';
+import MethodDetails from '@site/src/components/MethodDetails';
+
+# NitroliteRPC
+
+The NitroliteRPC provides a secure, reliable real-time communication protocol for state channel applications. It enables off-chain message exchange, state updates, and channel management. This system is built around the `NitroliteRPC` class, which provides the foundational methods for message construction, signing, parsing, and verification.
+
+
+
+
+
+
+## Core Logic: The `NitroliteRPC` Class
+
+The `NitroliteRPC` class is central to the RPC system. It offers a suite of static methods to handle the low-level details of the NitroliteRPC protocol.
+
+### Message Creation
+
+
+
+
+
+
+
+
+
+### Message Signing
+
+", description: "The RPC request object to sign." },
+ { name: "signer", type: "MessageSigner", description: "An async function that takes a message string and returns a Promise (signature)." }
+ ]}
+ returns="Promise>"
+ example={`// Assuming 'request' is a NitroliteRPCRequest and 'signer' is a MessageSigner
+const signedRequest = await NitroliteRPC.signRequestMessage(request, signer);`}
+/>
+
+", description: "The RPC response object to sign." },
+ { name: "signer", type: "MessageSigner", description: "An async function that takes a message string and returns a Promise (signature)." }
+ ]}
+ returns="Promise>"
+ example={`// Assuming 'response' is a NitroliteRPCResponse and 'signer' is a MessageSigner
+const signedResponse = await NitroliteRPC.signResponseMessage(response, signer);`}
+/>
+
+### Message Parsing & Validation
+
+
+
+These methods ensure that all communication adheres to the defined RPC structure and security requirements.
+
+## Generic Message Structure
+
+The `NitroliteRPC` class operates on messages adhering to the following general structures. For precise details on each field and for specific message types, please refer to the [RPC Type Definitions](./rpc_types).
+
+```typescript
+// Generic Request message structure
+{
+ "req": [requestId, method, params, timestamp], // Core request tuple
+ "int"?: Intent, // Optional intent for state changes
+ "acc"?: AccountID, // Optional account scope (channel/app ID)
+ "sig": [signature] // Array of signatures
+}
+
+// Generic Response message structure
+{
+ "res": [requestId, method, dataPayload, timestamp], // Core response tuple
+ "acc"?: AccountID, // Optional account scope
+ "int"?: Intent, // Optional intent
+ "sig"?: [signature] // Optional signatures for certain response types
+}
+```
+
+## Next Steps
+
+Dive deeper into the specifics of the RPC system:
+
+
+
+
+
\ No newline at end of file
diff --git a/erc7824-docs/docs/nitrolite_rpc/message_creation_api.md b/erc7824-docs/docs/nitrolite_rpc/message_creation_api.md
new file mode 100644
index 000000000..39e17ed53
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_rpc/message_creation_api.md
@@ -0,0 +1,403 @@
+---
+sidebar_position: 3
+title: RPC Message Creation API
+description: Detailed reference for functions that create specific, signed Nitrolite RPC request messages.
+keywords: [erc7824, statechannels, state channels, nitrolite, rpc, api, message creation, sdk]
+---
+
+import MethodDetails from '@site/src/components/MethodDetails';
+
+# RPC Message Creation API
+
+The functions detailed below are part of the `@erc7824/nitrolite` SDK and provide a convenient way to create fully formed, signed, and stringified JSON RPC request messages. These messages are ready for transmission over a WebSocket or any other transport layer communicating with a Nitrolite-compatible broker.
+
+Each function typically takes a `MessageSigner` (a function you provide to sign messages, usually integrating with a user's wallet) and other relevant parameters, then returns a `Promise` which resolves to the JSON string of the signed RPC message.
+
+## Authentication
+
+These functions are used for the client authentication flow with the broker. The typical sequence is:
+1. Client sends an `auth_request` (using `createAuthRequestMessage`).
+2. Broker responds with an `auth_challenge`.
+3. Client signs the challenge and sends an `auth_verify` (using `createAuthVerifyMessageFromChallenge` or `createAuthVerifyMessage`).
+4. Broker responds with `auth_success` or `auth_failure`.
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant Broker
+
+ Client->>Broker: auth_request (via createAuthRequestMessage)
+ Broker-->>Client: auth_challenge (response)
+ Client->>Broker: auth_verify (via createAuthVerifyMessage or createAuthVerifyMessageFromChallenge)
+ Broker-->>Client: auth_success / auth_failure (response)
+```
+
+
+
+
+
+
+
+## General & Keep-Alive
+
+Functions for general RPC interactions like keep-alive messages.
+
+
+
+## Query Operations
+
+Functions for retrieving information from the broker.
+
+
+
+
+
+
+
+
+
+## Application Session Management
+
+Functions for creating and closing application sessions (state channels).
+
+
+
+
+
+## Application-Specific Messaging
+
+Function for sending messages within an active application session.
+
+
+
+## Ledger Channel Management
+
+Functions for managing the underlying ledger channels (direct channels with the broker).
+
+
+
+
+
+## Advanced: Creating a Local Signer for Development
+
+To begin, you'll often need a fresh key pair (private key, public key, and address). The `generateKeyPair` utility can be used for this:
+
+```typescript
+import { ethers } from "ethers"; // Make sure ethers is installed
+
+// Definition (as provided in your example)
+interface CryptoKeypair {
+ privateKey: string;
+ publicKey: string;
+ address: string;
+}
+
+export const generateKeyPair = async (): Promise => {
+ try {
+ const wallet = ethers.Wallet.createRandom();
+ const privateKeyHash = ethers.utils.keccak256(wallet.privateKey);
+ const walletFromHashedKey = new ethers.Wallet(privateKeyHash);
+
+ return {
+ privateKey: privateKeyHash,
+ publicKey: walletFromHashedKey.publicKey,
+ address: walletFromHashedKey.address,
+ };
+ } catch (error) {
+ console.error('Error generating keypair, using fallback:', error);
+ const randomHex = ethers.utils.randomBytes(32);
+ const privateKey = ethers.utils.keccak256(randomHex);
+ const wallet = new ethers.Wallet(privateKey);
+
+ return {
+ privateKey: privateKey,
+ publicKey: wallet.publicKey,
+ address: wallet.address,
+ };
+ }
+};
+
+// Usage:
+async function main() {
+ const keyPair = await generateKeyPair();
+ console.log("Generated Private Key:", keyPair.privateKey);
+ console.log("Generated Address:", keyPair.address);
+ // Store keyPair.privateKey securely if you need to reuse this signer
+}
+```
+This function creates a new random wallet, hashes its private key for deriving a new one (a common pattern for deterministic key generation or added obfuscation, though the security implications depend on the exact use case), and returns the private key, public key, and address.
+
+### Creating a Signer from a Private Key
+
+Once you have a private key (either generated as above or from a known development account), you can create a `MessageSigner` compatible with the RPC message creation functions. The `MessageSigner` interface typically expects an asynchronous `sign` method.
+
+```typescript
+import { ethers } from "ethers";
+import { Hex } from "viem"; // Assuming Hex type is from viem or similar
+
+// Definitions (as provided in your example)
+type RequestData = unknown; // Placeholder for actual request data type
+type ResponsePayload = unknown; // Placeholder for actual response payload type
+
+interface WalletSigner {
+ publicKey: string;
+ address: Hex;
+ sign: (payload: RequestData | ResponsePayload) => Promise;
+}
+
+export const createEthersSigner = (privateKey: string): WalletSigner => {
+ try {
+ const wallet = new ethers.Wallet(privateKey);
+
+ return {
+ publicKey: wallet.publicKey,
+ address: wallet.address as Hex,
+ sign: async (payload: RequestData | ResponsePayload): Promise => {
+ try {
+ // The NitroliteRPC.hashMessage method should ideally be used here
+ // to ensure the exact same hashing logic as the SDK internals.
+ // For demonstration, using a generic hashing approach:
+ const messageToSign = JSON.stringify(payload);
+ const messageHash = ethers.utils.id(messageToSign); // ethers.utils.id performs keccak256
+ const messageBytes = ethers.utils.arrayify(messageHash);
+
+ const flatSignature = await wallet._signingKey().signDigest(messageBytes);
+ const signature = ethers.utils.joinSignature(flatSignature);
+ return signature as Hex;
+ } catch (error) {
+ console.error('Error signing message:', error);
+ throw error;
+ }
+ },
+ };
+ } catch (error) {
+ console.error('Error creating ethers signer:', error);
+ throw error;
+ }
+};
+
+// Usage:
+async function setupSigner() {
+ const keyPair = await generateKeyPair(); // Or use a known private key
+ const localSigner = createEthersSigner(keyPair.privateKey);
+
+ // Now 'localSigner' can be passed to the RPC message creation functions:
+ // const authRequest = await createAuthRequestMessage(localSigner.sign, localSigner.address as Address);
+ // console.log("Auth Request with local signer:", authRequest);
+}
+```
+
+**Important Considerations for `createEthersSigner`:**
+* **Hashing Consistency:** The `sign` method within `createEthersSigner` must hash the payload in a way that is **identical** to how the `NitroliteRPC` class (specifically `NitroliteRPC.hashMessage`) expects messages to be hashed before signing. The example above uses `ethers.utils.id(JSON.stringify(payload))`. It's crucial to verify if the SDK's internal hashing uses a specific message prefix (e.g., EIP-191 personal_sign prefix) or a different serialization method. If the SDK does *not* use a standard EIP-191 prefix, or uses a custom one, your local signer's hashing logic must replicate this exactly for signatures to be valid. Using `NitroliteRPC.hashMessage(payload)` directly (if `payload` matches the `NitroliteRPCMessage` structure) is the safest way to ensure consistency.
+* **Type Compatibility:** Ensure the `Address` type expected by functions like `createAuthRequestMessage` is compatible with `localSigner.address`. The example uses `localSigner.address as Address` assuming `Address` is `0x${string}`.
+* **Error Handling:** The provided examples include basic error logging. Robust applications should implement more sophisticated error handling.
diff --git a/erc7824-docs/docs/nitrolite_rpc/rpc_types.md b/erc7824-docs/docs/nitrolite_rpc/rpc_types.md
new file mode 100644
index 000000000..2848c3156
--- /dev/null
+++ b/erc7824-docs/docs/nitrolite_rpc/rpc_types.md
@@ -0,0 +1,296 @@
+---
+sidebar_position: 4 # Adjusted sidebar_position if message_creation_api is 3
+title: RPC Type Definitions
+description: Comprehensive type definitions for the Nitrolite RPC protocol.
+keywords: [erc7824, statechannels, state channels, nitrolite, rpc, types, typescript, protocol, api, definitions]
+---
+
+# Nitrolite RPC Type Definitions
+
+This page provides a comprehensive reference for all TypeScript types, interfaces, and enums used by the Nitrolite RPC system, as defined in the `@erc7824/nitrolite` SDK. These definitions are crucial for understanding the structure of messages exchanged with the Nitrolite broker.
+
+## Core Types
+
+These are fundamental types used throughout the RPC system.
+
+### `RequestID`
+A unique identifier for an RPC request. Typically a number.
+```typescript
+export type RequestID = number;
+```
+
+### `Timestamp`
+Represents a Unix timestamp in milliseconds. Used for message ordering and security.
+```typescript
+export type Timestamp = number;
+```
+
+### `AccountID`
+A unique identifier for a channel or application session, represented as a hexadecimal string.
+```typescript
+export type AccountID = Hex; // from 'viem'
+```
+
+### `Intent`
+Represents the allocation intent change as an array of big integers. This is used to specify how funds should be re-distributed in a state update.
+```typescript
+export type Intent = bigint[];
+```
+
+## Message Payloads
+
+These types define the core data arrays within RPC messages.
+
+### `RequestData`
+The structured data payload within a request message.
+```typescript
+export type RequestData = [RequestID, string, any[], Timestamp?];
+```
+- `RequestID`: The unique ID of this request.
+- `string`: The name of the RPC method being called.
+- `any[]`: An array of parameters for the method.
+- `Timestamp?`: An optional timestamp for when the request was created.
+
+### `ResponseData`
+The structured data payload within a successful response message.
+```typescript
+export type ResponseData = [RequestID, string, any[], Timestamp?];
+```
+- `RequestID`: The ID of the original request this response is for.
+- `string`: The name of the original RPC method.
+- `any[]`: An array containing the result(s) of the method execution.
+- `Timestamp?`: An optional timestamp for when the response was created.
+
+### `NitroliteRPCErrorDetail`
+Defines the structure of the error object within an error response.
+```typescript
+export interface NitroliteRPCErrorDetail {
+ error: string;
+}
+```
+- `error`: A string describing the error that occurred.
+
+### `ErrorResponseData`
+The structured data payload for an error response message.
+```typescript
+export type ErrorResponseData = [RequestID, "error", [NitroliteRPCErrorDetail], Timestamp?];
+```
+- `RequestID`: The ID of the original request this error is for.
+- `"error"`: A literal string indicating this is an error response.
+- `[NitroliteRPCErrorDetail]`: An array containing a single `NitroliteRPCErrorDetail` object.
+- `Timestamp?`: An optional timestamp for when the error response was created.
+
+### `ResponsePayload`
+A union type representing the payload of a response, which can be either a success (`ResponseData`) or an error (`ErrorResponseData`).
+```typescript
+export type ResponsePayload = ResponseData | ErrorResponseData;
+```
+
+## Message Envelopes
+
+These interfaces define the overall structure of messages sent over the wire.
+
+### `NitroliteRPCMessage`
+The base wire format for Nitrolite RPC messages.
+```typescript
+export interface NitroliteRPCMessage {
+ req?: RequestData;
+ res?: ResponsePayload;
+ int?: Intent;
+ sig?: Hex[];
+}
+```
+- `req?`: The request payload, if this is a request message.
+- `res?`: The response payload, if this is a response message.
+- `int?`: Optional allocation intent change.
+- `sig?`: Optional array of cryptographic signatures (hex strings).
+
+## Parsing Results
+
+### `ParsedResponse`
+Represents the result of parsing an incoming Nitrolite RPC response message.
+```typescript
+export interface ParsedResponse {
+ isValid: boolean;
+ error?: string;
+ isError?: boolean;
+ requestId?: RequestID;
+ method?: string;
+ data?: any[] | NitroliteRPCErrorDetail;
+ acc?: AccountID;
+ int?: Intent;
+ timestamp?: Timestamp;
+}
+```
+- `isValid`: `true` if the message was successfully parsed and passed basic structural validation.
+- `error?`: If `isValid` is `false`, contains a description of the parsing or validation error.
+- `isError?`: `true` if the parsed response represents an error (i.e., `method === "error"`). Undefined if `isValid` is `false`.
+- `requestId?`: The `RequestID` from the response payload. Undefined if the structure is invalid.
+- `method?`: The method name from the response payload. Undefined if the structure is invalid.
+- `data?`: The extracted data payload (result array for success, `NitroliteRPCErrorDetail` object for error). Undefined if the structure is invalid or the error payload is malformed.
+- `acc?`: The `AccountID` from the message envelope, if present.
+- `int?`: The `Intent` from the message envelope, if present.
+- `timestamp?`: The `Timestamp` from the response payload. Undefined if the structure is invalid.
+
+## Request Parameter Structures
+
+These interfaces define the expected parameters for specific RPC methods.
+
+### `AppDefinition`
+Defines the structure of an application's configuration.
+```typescript
+export interface AppDefinition {
+ protocol: string;
+ participants: Hex[];
+ weights: number[];
+ quorum: number;
+ challenge: number;
+ nonce?: number;
+}
+```
+- `protocol`: The protocol identifier or name for the application logic (e.g., `"NitroRPC/0.2"`).
+- `participants`: An array of participant addresses (Ethereum addresses as `Hex`) involved in the application.
+- `weights`: An array representing the relative weights or stakes of participants. Order corresponds to the `participants` array.
+- `quorum`: The number/percentage of participants (based on weights) required to reach consensus.
+- `challenge`: A parameter related to the challenge period or mechanism (e.g., duration in seconds).
+- `nonce?`: An optional unique number (nonce) used to ensure the uniqueness of the application instance and prevent replay attacks.
+
+### `CreateAppSessionRequest`
+Parameters for the `create_app_session` RPC method.
+```typescript
+export interface CreateAppSessionRequest {
+ definition: AppDefinition;
+ token: Hex;
+ allocations: bigint[];
+}
+```
+- `definition`: The `AppDefinition` object detailing the application being created.
+- `token`: The `Hex` address of the ERC20 token contract used for allocations within this application session.
+- `allocations`: An array of `bigint` representing the initial allocation distribution among participants. The order corresponds to the `participants` array in the `definition`.
+
+Example:
+```json
+{
+ "definition": {
+ "protocol": "NitroRPC/0.2",
+ "participants": [
+ "0xAaBbCcDdEeFf0011223344556677889900aAbBcC",
+ "0x00112233445566778899AaBbCcDdEeFf00112233"
+ ],
+ "weights": [100, 0], // Example: Participant 1 has 100% weight
+ "quorum": 100, // Example: 100% quorum needed
+ "challenge": 86400, // Example: 1 day challenge period
+ "nonce": 12345
+ },
+ "token": "0xTokenContractAddress00000000000000000000",
+ "allocations": ["1000000000000000000", "0"] // 1 Token for P1, 0 for P2 (as strings for bigint)
+}
+```
+
+### `CloseAppSessionRequest`
+Parameters for the `close_app_session` RPC method.
+```typescript
+export interface CloseAppSessionRequest {
+ app_id: Hex;
+ allocations: bigint[];
+}
+```
+- `app_id`: The unique `AccountID` (as `Hex`) of the application session to be closed.
+- `allocations`: An array of `bigint` representing the final allocation distribution among participants upon closing. Order corresponds to the `participants` array in the application's definition.
+
+### `ResizeChannel`
+Parameters for the `resize_channel` RPC method.
+```typescript
+export interface ResizeChannel {
+ channel_id: Hex;
+ participant_change: bigint;
+ funds_destination: Hex;
+}
+```
+- `channel_id`: The unique `AccountID` (as `Hex`) of the direct ledger channel to be resized.
+- `participant_change`: The `bigint` amount by which the participant's allocation in the channel should change (positive to add funds, negative to remove).
+- `funds_destination`: The `Hex` address where funds will be sent if `participant_change` is negative (withdrawal), or the source of funds if positive (though typically handled by prior on-chain deposit).
+
+## Function Types (Signers & Verifiers)
+
+These types define the signatures for functions used in cryptographic operations.
+
+### `MessageSigner`
+A function that signs a message payload.
+```typescript
+export type MessageSigner = (payload: RequestData | ResponsePayload) => Promise;
+```
+- Takes a `RequestData` or `ResponsePayload` object (the array part of the message).
+- Returns a `Promise` that resolves to the cryptographic signature as a `Hex` string.
+
+### `SingleMessageVerifier`
+A function that verifies a single message signature.
+```typescript
+export type SingleMessageVerifier = (
+ payload: RequestData | ResponsePayload,
+ signature: Hex,
+ address: Address // from 'viem'
+) => Promise;
+```
+- Takes the `RequestData` or `ResponsePayload` object, the `Hex` signature, and the expected signer's `Address`.
+- Returns a `Promise` that resolves to `true` if the signature is valid for the given payload and address, `false` otherwise.
+
+## Usage Examples
+
+### Creating Message Payloads and Envelopes
+```typescript
+// Example Request Payload (for a 'ping' method)
+const pingRequestData: RequestData = [1, "ping", []]; // Assuming timestamp is added by sender utility
+
+// Example Request Envelope
+const pingRequestMessage: NitroliteRPCMessage = {
+ req: pingRequestData,
+ // sig: ["0xSignatureIfPreSigned..."] // Signature added by signing utility
+};
+
+// Example Application-Specific Request
+const appActionData: RequestData = [2, "message", [{ move: "rock" }], Date.now()];
+const appActionMessage: ApplicationRPCMessage = {
+ sid: "0xAppSessionId...",
+ req: appActionData,
+ // sig: ["0xSignature..."]
+};
+
+// Example Successful Response Payload
+const pongResponseData: ResponseData = [1, "ping", ["pong"], Date.now()];
+
+// Example Error Detail
+const errorDetail: NitroliteRPCErrorDetail = { error: "Method parameters are invalid." };
+
+// Example Error Response Payload
+const errorResponseData: ErrorResponseData = [2, "error", [errorDetail], Date.now()];
+
+// Example Response Envelope (Success)
+const successResponseEnvelope: NitroliteRPCMessage = {
+ res: pongResponseData,
+};
+```
+
+### Working with Signers (Conceptual)
+```typescript
+// Conceptual: How a MessageSigner might be used
+async function signAndSend(payload: RequestData, signer: MessageSigner, sendMessageToServer: (msg: string) => void) {
+ const signature = await signer(payload);
+ const message: NitroliteRPCMessage = {
+ req: payload,
+ sig: [signature]
+ };
+ sendMessageToServer(JSON.stringify(message));
+}
+```
+
+## Implementation Considerations
+
+When working with these types:
+
+1. **Serialization**: Messages are typically serialized to JSON strings for transmission (e.g., over WebSockets).
+2. **Signing**: Payloads (`req` or `res` arrays) are what get signed, not the entire envelope. The resulting signature is then added to the `sig` field of the `NitroliteRPCMessage` envelope.
+3. **Validation**: Always validate the structure and types of incoming messages against these definitions, preferably using utilities provided by the SDK.
+4. **Error Handling**: Properly check for `isError` in `ParsedResponse` and use `NitroliteErrorCode` to understand the nature of failures.
+5. **BigInts**: Note the use of `bigint` for `Intent` and allocation amounts. Ensure your environment and serialization/deserialization logic handle `bigint` correctly (e.g., converting to/from strings for JSON).
+6. **Hex Strings**: Types like `AccountID`, `Hex` (for signatures, token addresses) imply hexadecimal string format (e.g., `"0x..."`).
diff --git a/erc7824-docs/docs/quick_start/application_session.md b/erc7824-docs/docs/quick_start/application_session.md
new file mode 100644
index 000000000..08f1e1e0c
--- /dev/null
+++ b/erc7824-docs/docs/quick_start/application_session.md
@@ -0,0 +1,1071 @@
+---
+sidebar_position: 7
+title: Create Application Sessions
+description: Create and manage off-chain application sessions to interact with ClearNodes.
+keywords: [erc7824, nitrolite, application session, state channels, app session]
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# Create Application Sessions
+
+After connecting to a ClearNode and checking your channel balances, you can create application sessions to interact with specific applications on the state channel network. Application sessions allow you to perform off-chain transactions and define custom behavior for your interactions.
+
+## Understanding Application Sessions
+
+```mermaid
+graph TD
+ A[Create Application Session] --> B[Session Active]
+ B --> C[Off-Chain Transactions]
+ C --> D[Close Session]
+```
+
+Application sessions in Nitrolite allow you to:
+
+- Create isolated environments for specific interactions
+- Define rules for off-chain transactions
+- Specify how funds are allocated between participants
+- Implement custom application logic and state management
+
+An application session serves as a mechanism to track and manage interactions between participants, with the ClearNode acting as a facilitator.
+
+## Creating an Application Session
+
+To create an application session, you'll use the `createAppSessionMessage` helper from NitroliteRPC. Here's how to do it:
+
+
+
+
+```javascript
+import { createAppSessionMessage, parseRPCResponse, MessageSigner, CreateAppSessionRPCParams } from '@erc7824/nitrolite';
+import { useCallback } from 'react';
+import { Address } from 'viem';
+
+function useCreateApplicationSession() {
+ const createApplicationSession = useCallback(
+ async (
+ signer: MessageSigner,
+ sendRequest: (message: string) => Promise,
+ participantA: Address,
+ participantB: Address,
+ amount: string,
+ ) => {
+ try {
+ // Define the application parameters
+ const appDefinition = {
+ protocol: 'nitroliterpc',
+ participants: [participantA, participantB],
+ weights: [100, 0], // Weight distribution for consensus
+ quorum: 100, // Required consensus percentage
+ challenge: 0, // Challenge period
+ nonce: Date.now(), // Unique identifier
+ };
+
+ // Define allocations with asset type instead of token address
+ const allocations = [
+ {
+ participant: participantA,
+ asset: 'usdc',
+ amount: amount,
+ },
+ {
+ participant: participantB,
+ asset: 'usdc',
+ amount: '0',
+ },
+ ];
+
+ // Create a signed message using the createAppSessionMessage helper
+ const signedMessage = await createAppSessionMessage(
+ signer,
+ [
+ {
+ definition: appDefinition,
+ allocations: allocations,
+ },
+ ]
+ );
+
+ // Send the signed message to the ClearNode
+ const response = await sendRequest(signedMessage);
+
+ // Handle the response
+ if (response.app_session_id) {
+ // Store the app session ID for future reference
+ localStorage.setItem('app_session_id', response.app_session_id);
+ return { success: true, app_session_id: response.app_session_id, response };
+ } else {
+ return { success: true, response };
+ }
+ } catch (error) {
+ console.error('Error creating application session:', error);
+ return {
+ success: false,
+ error: error instanceof Error
+ ? error.message
+ : 'Unknown error during session creation',
+ };
+ }
+ },
+ []
+ );
+
+ return { createApplicationSession };
+}
+
+// Usage example
+function MyComponent() {
+ const { createApplicationSession } = useCreateApplicationSession();
+
+ const handleCreateSession = async () => {
+ // Define your WebSocket send wrapper
+ const sendRequest = async (payload: string) => {
+ return new Promise((resolve, reject) => {
+ // Assuming ws is your WebSocket connection
+ const handleMessage = (event) => {
+ try {
+ const message = parseRPCResponse(event.data);
+ if (message.method === RPCMethod.CreateAppSession) {
+ ws.removeEventListener('message', handleMessage);
+ resolve(message.params);
+ }
+ } catch (error) {
+ console.error('Error parsing message:', error);
+ }
+ };
+
+ ws.addEventListener('message', handleMessage);
+ ws.send(payload);
+
+ // Set timeout to prevent hanging
+ setTimeout(() => {
+ ws.removeEventListener('message', handleMessage);
+ reject(new Error('App session creation timeout'));
+ }, 10000);
+ });
+ };
+
+ const result = await createApplicationSession(
+ walletSigner, // Your signer object
+ sendRequest, // Function to send the request
+ '0xYourAddress', // Your address
+ '0xOtherAddress', // Other participant's address
+ '100', // Amount
+ );
+
+ if (result.success) {
+ console.log(`Application session created with ID: ${result.app_session_id}`);
+ } else {
+ console.error(`Failed to create application session: ${result.error}`);
+ }
+ };
+
+ return (
+
+ );
+}
+```
+
+
+
+
+```typescript
+// app-session.service.ts
+import { Injectable } from '@angular/core';
+import { createAppSessionMessage } from '@erc7824/nitrolite';
+import { ethers } from 'ethers';
+import { BehaviorSubject, Observable, from } from 'rxjs';
+import { tap, catchError } from 'rxjs/operators';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AppSessionService {
+ private webSocket: WebSocket | null = null;
+ private appIdSubject = new BehaviorSubject(null);
+
+ public appId$ = this.appIdSubject.asObservable();
+
+ constructor() {
+ // Retrieve app ID from storage if available
+ const storedAppId = localStorage.getItem('app_session_id');
+ if (storedAppId) {
+ this.appIdSubject.next(storedAppId);
+ }
+ }
+
+ public setWebSocket(ws: WebSocket): void {
+ this.webSocket = ws;
+ }
+
+ public createApplicationSession(
+ signer: any,
+ participantA: string,
+ participantB: string,
+ amount: string,
+ ): Observable {
+ if (!this.webSocket) {
+ throw new Error('WebSocket connection is not established');
+ }
+
+ return from(this.createAppSessionAsync(
+ signer,
+ participantA,
+ participantB,
+ amount,
+ )).pipe(
+ tap(result => {
+ if (result.success && result.app_session_id) {
+ localStorage.setItem('app_session_id', result.app_session_id);
+ this.appIdSubject.next(result.app_session_id);
+ }
+ }),
+ catchError(error => {
+ console.error('Error creating application session:', error);
+ throw error;
+ })
+ );
+ }
+
+ private async createAppSessionAsync(
+ signer: any,
+ participantA: string,
+ participantB: string,
+ amount: string,
+ ): Promise {
+ try {
+
+ // Define the application parameters
+ const appDefinition = {
+ protocol: 'nitroliterpc',
+ participants: [participantA, participantB],
+ weights: [100, 0],
+ quorum: 100,
+ challenge: 0,
+ nonce: Date.now(),
+ };
+
+ // Define the allocations with asset type
+ const allocations = [
+ {
+ participant: participantA,
+ asset: 'usdc',
+ amount: amount,
+ },
+ {
+ participant: participantB,
+ asset: 'usdc',
+ amount: '0',
+ },
+ ];
+
+ // Create message signer function
+ const messageSigner = async (payload: any) => {
+ const message = JSON.stringify(payload);
+ const digestHex = ethers.id(message);
+ const messageBytes = ethers.getBytes(digestHex);
+ const signature = await signer.signMessage(messageBytes);
+ return signature;
+ };
+
+ // Create the signed message
+ const signedMessage = await createAppSessionMessage(
+ messageSigner,
+ [
+ {
+ definition: appDefinition,
+ allocations: allocations,
+ },
+ ]
+ );
+
+ // Send the message and wait for response
+ return await this.sendRequest(signedMessage);
+ } catch (error) {
+ console.error('Error in createAppSessionAsync:', error);
+ return {
+ success: false,
+ error: error instanceof Error ? error.message : 'Unknown error'
+ };
+ }
+ }
+
+ private sendRequest(payload: string): Promise {
+ return new Promise((resolve, reject) => {
+ if (!this.webSocket) {
+ reject(new Error('WebSocket not connected'));
+ return;
+ }
+
+ const handleMessage = (event: MessageEvent) => {
+ try {
+ const message = JSON.parse(event.data);
+ if (message.res && message.res[1] === 'create_app_session') {
+ this.webSocket?.removeEventListener('message', handleMessage);
+ resolve({
+ success: true,
+ app_session_id: message.res[2]?.[0]?.app_session_id || null,
+ status: message.res[2]?.[0]?.status || "open",
+ response: message.res[2]
+ });
+ }
+
+ if (message.err) {
+ this.webSocket?.removeEventListener('message', handleMessage);
+ reject(new Error(`Error: ${message.err[1]} - ${message.err[2]}`));
+ }
+ } catch (error) {
+ console.error('Error parsing message:', error);
+ }
+ };
+
+ this.webSocket.addEventListener('message', handleMessage);
+ this.webSocket.send(payload);
+
+ // Set timeout to prevent hanging
+ setTimeout(() => {
+ this.webSocket?.removeEventListener('message', handleMessage);
+ reject(new Error('App session creation timeout'));
+ }, 10000);
+ });
+ }
+}
+
+// app-session.component.ts
+import { Component, OnInit } from '@angular/core';
+import { AppSessionService } from './app-session.service';
+
+@Component({
+ selector: 'app-session-creator',
+ template: `
+