From ba3ef5ff5cfbe809667a76212b9cf7aa23cce4a7 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Wed, 18 Feb 2026 14:29:10 +0100 Subject: [PATCH 1/9] refactor: restructure packages, add Makefile for binding generation --- .gitignore | 3 + .gitmodules | 2 +- Makefile | 28 + chain/IWithdraw.abi | 1 - chain/README.md | 62 -- chain/SimpleCustody.abi | 528 --------------- chain/SimpleCustody.bin | 1 - chain/gen.go | 4 - chain/listener.go | 199 ------ chain/listener_test.go | 399 ----------- chain/simulated_backend_test.go | 168 ----- cmd/example/usage.go | 182 ----- cmd/nitewatch/config.yaml | 19 + cmd/nitewatch/limits.yaml | 5 - cmd/nitewatch/main.go | 180 +---- config/config.go | 114 ++++ contracts/evm/lib/forge-std | 2 +- contracts/evm/src/MockERC20.sol | 12 + core/checker.go | 119 ---- core/checker_test.go | 314 --------- custody/anviltest/anviltest.go | 146 ++++ custody/ethlistener.go | 263 ++++++++ custody/ideposit.go | 356 ++++++++++ {chain => custody}/iwithdraw.go | 2 +- custody/listener.go | 134 ++++ custody/mock_erc20.go | 781 ++++++++++++++++++++++ {chain => custody}/simple_custody.go | 4 +- interfaces.go => custody/types.go | 30 +- go.mod | 137 ++-- go.sum | 350 +++++----- internal/checker/checker.go | 151 +++++ internal/checker/checker_test.go | 281 ++++++++ internal/store/adapter.go | 139 ++++ {store => internal/store}/adapter_test.go | 150 ++--- service/service.go | 344 ++++++++++ service/web_handlers.go | 92 +++ store/adapter.go | 72 -- 37 files changed, 3243 insertions(+), 2531 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile delete mode 100644 chain/IWithdraw.abi delete mode 100644 chain/README.md delete mode 100644 chain/SimpleCustody.abi delete mode 100644 chain/SimpleCustody.bin delete mode 100644 chain/gen.go delete mode 100644 chain/listener.go delete mode 100644 chain/listener_test.go delete mode 100644 chain/simulated_backend_test.go delete mode 100644 cmd/example/usage.go create mode 100644 cmd/nitewatch/config.yaml delete mode 100644 cmd/nitewatch/limits.yaml create mode 100644 config/config.go create mode 100644 contracts/evm/src/MockERC20.sol delete mode 100644 core/checker.go delete mode 100644 core/checker_test.go create mode 100644 custody/anviltest/anviltest.go create mode 100644 custody/ethlistener.go create mode 100644 custody/ideposit.go rename {chain => custody}/iwithdraw.go (99%) create mode 100644 custody/listener.go create mode 100644 custody/mock_erc20.go rename {chain => custody}/simple_custody.go (99%) rename interfaces.go => custody/types.go (67%) create mode 100644 internal/checker/checker.go create mode 100644 internal/checker/checker_test.go create mode 100644 internal/store/adapter.go rename {store => internal/store}/adapter_test.go (56%) create mode 100644 service/service.go create mode 100644 service/web_handlers.go delete mode 100644 store/adapter.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b628f4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Intermediate contract artifacts (generated by Makefile) +custody/*.abi +custody/*.bin diff --git a/.gitmodules b/.gitmodules index 0c03bd8..13c20a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/foundry-rs/forge-std [submodule "contracts/evm/lib/openzeppelin-contracts"] path = contracts/evm/lib/openzeppelin-contracts - url = https://github.com/OpenZeppelin/openzeppelin-contracts.git + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7c7fcd0 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +SOL_SOURCES := $(shell find contracts/evm/src -name '*.sol') +BINDINGS := custody/iwithdraw.go custody/ideposit.go custody/simple_custody.go custody/mock_erc20.go + +.PHONY: generate +generate: $(BINDINGS) + +# Sentinel tracks forge build; only re-runs when .sol sources change. +contracts/evm/out/.build-sentinel: $(SOL_SOURCES) + cd contracts/evm && forge build + @touch $@ + +custody/iwithdraw.go: contracts/evm/out/.build-sentinel + jq .abi contracts/evm/out/IWithdraw.sol/IWithdraw.json > custody/IWithdraw.abi + abigen --abi custody/IWithdraw.abi --pkg custody --type IWithdraw --out $@ + +custody/ideposit.go: contracts/evm/out/.build-sentinel + jq .abi contracts/evm/out/IDeposit.sol/IDeposit.json > custody/IDeposit.abi + abigen --abi custody/IDeposit.abi --pkg custody --type IDeposit --out $@ + +custody/simple_custody.go: contracts/evm/out/.build-sentinel + jq .abi contracts/evm/out/SimpleCustody.sol/SimpleCustody.json > custody/SimpleCustody.abi + jq -r .bytecode.object contracts/evm/out/SimpleCustody.sol/SimpleCustody.json > custody/SimpleCustody.bin + abigen --abi custody/SimpleCustody.abi --bin custody/SimpleCustody.bin --pkg custody --type SimpleCustody --out $@ + +custody/mock_erc20.go: contracts/evm/out/.build-sentinel + jq .abi contracts/evm/out/MockERC20.sol/MockERC20.json > custody/MockERC20.abi + jq -r .bytecode.object contracts/evm/out/MockERC20.sol/MockERC20.json > custody/MockERC20.bin + abigen --abi custody/MockERC20.abi --bin custody/MockERC20.bin --pkg custody --type MockERC20 --out $@ diff --git a/chain/IWithdraw.abi b/chain/IWithdraw.abi deleted file mode 100644 index 08fc672..0000000 --- a/chain/IWithdraw.abi +++ /dev/null @@ -1 +0,0 @@ -[{"type":"function","name":"finalizeWithdraw","inputs":[{"name":"withdrawalId","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"rejectWithdraw","inputs":[{"name":"withdrawalId","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"startWithdraw","inputs":[{"name":"user","type":"address","internalType":"address"},{"name":"token","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"},{"name":"nonce","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"withdrawalId","type":"bytes32","internalType":"bytes32"}],"stateMutability":"nonpayable"},{"type":"event","name":"WithdrawFinalized","inputs":[{"name":"withdrawalId","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"success","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"type":"event","name":"WithdrawStarted","inputs":[{"name":"withdrawalId","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"token","type":"address","indexed":true,"internalType":"address"},{"name":"amount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"nonce","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"error","name":"ETHTransferFailed","inputs":[]},{"type":"error","name":"InsufficientLiquidity","inputs":[]},{"type":"error","name":"WithdrawalAlreadyExists","inputs":[]},{"type":"error","name":"WithdrawalAlreadyFinalized","inputs":[]},{"type":"error","name":"WithdrawalNotFound","inputs":[]},{"type":"error","name":"ZeroAmount","inputs":[]}] diff --git a/chain/README.md b/chain/README.md deleted file mode 100644 index cdf275b..0000000 --- a/chain/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# chain - -The `chain` package provides Go bindings and a high-level listener for the `ICustody` smart contract events. - -## Listener - -The `Listener` struct allows you to subscribe to contract events and wait for a specified number of block confirmations before processing them. This is crucial for ensuring that your application only acts on finalized transactions, mitigating the risk of chain reorgs. - -### Usage - -```go -package main - -import ( - "context" - "log" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/layer-3/nitewatch/chain" -) - -func main() { - // 1. Connect to an Ethereum node (must support subscriptions, e.g., via WebSocket) - client, err := ethclient.Dial("ws://127.0.0.1:8545") - if err != nil { - log.Fatalf("Failed to connect to the Ethereum client: %v", err) - } - - // 2. Create a new Listener - // address: The deployed ICustody contract address - // confirmations: Number of blocks to wait for confirmation (e.g., 12 for mainnet) - contractAddress := common.HexToAddress("0x...") - listener, err := chain.NewListener(client, contractAddress, 12) - if err != nil { - log.Fatalf("Failed to create listener: %v", err) - } - - // 3. Subscribe to Deposited events - ctx := context.Background() - deposits := make(chan *chain.ICustodyDeposited) - - go func() { - if err := listener.WatchDeposited(ctx, deposits); err != nil { - log.Printf("WatchDeposited error: %v", err) - } - }() - - // 4. Process confirmed events - for event := range deposits { - log.Printf("Confirmed deposit: User=%s Token=%s Amount=%s Block=%d", - event.User.Hex(), event.Token.Hex(), event.Amount.String(), event.Raw.BlockNumber) - } -} -``` - -## Confirmations - -The `confirmations` parameter in `NewListener` determines how many blocks must be mined on top of the block containing the event before it is emitted. - -- `confirmations = 0`: Events are emitted immediately upon detection. Reorgs are not handled (removed events are ignored). -- `confirmations > 0`: Events are buffered until the chain reaches the required depth. If a reorg occurs and the event is removed from the canonical chain during the confirmation period, it is discarded and never emitted. diff --git a/chain/SimpleCustody.abi b/chain/SimpleCustody.abi deleted file mode 100644 index c1c44f0..0000000 --- a/chain/SimpleCustody.abi +++ /dev/null @@ -1,528 +0,0 @@ -[ - { - "type": "constructor", - "inputs": [ - { - "name": "admin", - "type": "address", - "internalType": "address" - }, - { - "name": "neodax", - "type": "address", - "internalType": "address" - }, - { - "name": "nitewatch", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "DEFAULT_ADMIN_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "NEODAX_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "NITEWATCH_ROLE", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "deposit", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "finalizeWithdraw", - "inputs": [ - { - "name": "withdrawalId", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "getRoleAdmin", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "grantRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "hasRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "rejectWithdraw", - "inputs": [ - { - "name": "withdrawalId", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "renounceRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "callerConfirmation", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "revokeRole", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "startWithdraw", - "inputs": [ - { - "name": "user", - "type": "address", - "internalType": "address" - }, - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "nonce", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "withdrawalId", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "supportsInterface", - "inputs": [ - { - "name": "interfaceId", - "type": "bytes4", - "internalType": "bytes4" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "withdrawals", - "inputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "outputs": [ - { - "name": "user", - "type": "address", - "internalType": "address" - }, - { - "name": "token", - "type": "address", - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "exists", - "type": "bool", - "internalType": "bool" - }, - { - "name": "finalized", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "event", - "name": "Deposited", - "inputs": [ - { - "name": "user", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "token", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleAdminChanged", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "previousAdminRole", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "newAdminRole", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleGranted", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "sender", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "RoleRevoked", - "inputs": [ - { - "name": "role", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "account", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "sender", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "WithdrawFinalized", - "inputs": [ - { - "name": "withdrawalId", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "success", - "type": "bool", - "indexed": false, - "internalType": "bool" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "WithdrawStarted", - "inputs": [ - { - "name": "withdrawalId", - "type": "bytes32", - "indexed": true, - "internalType": "bytes32" - }, - { - "name": "user", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "token", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amount", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "nonce", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "AccessControlBadConfirmation", - "inputs": [] - }, - { - "type": "error", - "name": "AccessControlUnauthorizedAccount", - "inputs": [ - { - "name": "account", - "type": "address", - "internalType": "address" - }, - { - "name": "neededRole", - "type": "bytes32", - "internalType": "bytes32" - } - ] - }, - { - "type": "error", - "name": "ETHTransferFailed", - "inputs": [] - }, - { - "type": "error", - "name": "InsufficientLiquidity", - "inputs": [] - }, - { - "type": "error", - "name": "MsgValueMismatch", - "inputs": [] - }, - { - "type": "error", - "name": "NonZeroMsgValueForERC20", - "inputs": [] - }, - { - "type": "error", - "name": "ReentrancyGuardReentrantCall", - "inputs": [] - }, - { - "type": "error", - "name": "SafeERC20FailedOperation", - "inputs": [ - { - "name": "token", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "WithdrawalAlreadyExists", - "inputs": [] - }, - { - "type": "error", - "name": "WithdrawalAlreadyFinalized", - "inputs": [] - }, - { - "type": "error", - "name": "WithdrawalNotFound", - "inputs": [] - }, - { - "type": "error", - "name": "ZeroAmount", - "inputs": [] - } -] diff --git a/chain/SimpleCustody.bin b/chain/SimpleCustody.bin deleted file mode 100644 index 03059da..0000000 --- a/chain/SimpleCustody.bin +++ /dev/null @@ -1 +0,0 @@ -0x608060405234801561000f575f5ffd5b50604051611d9c380380611d9c833981810160405281019061003191906102c1565b600161004f6100446100d260201b60201c565b6100fb60201b60201c565b5f01819055506100675f5f1b8461010460201b60201c565b506100987f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f38361010460201b60201c565b506100c97ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa8261010460201b60201c565b50505050610311565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b5f61011583836101f960201b60201c565b6101ef5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061018c61025c60201b60201c565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600190506101f3565b5f90505b92915050565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f33905090565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61029082610267565b9050919050565b6102a081610286565b81146102aa575f5ffd5b50565b5f815190506102bb81610297565b92915050565b5f5f5f606084860312156102d8576102d7610263565b5b5f6102e5868287016102ad565b93505060206102f6868287016102ad565b9250506040610307868287016102ad565b9150509250925092565b611a7e8061031e5f395ff3fe6080604052600436106100dc575f3560e01c80635a98c2231161007e578063d547741f11610058578063d547741f146102a4578063d87e1f41146102cc578063da86f31514610308578063efbf64a714610332576100dc565b80635a98c2231461021457806391d148541461023e578063a217fddf1461027a576100dc565b8063248a9ca3116100ba578063248a9ca31461016c5780632f2ff15d146101a857806336568abe146101d057806347e7ef24146101f8576100dc565b806301ffc9a7146100e057806305e95be71461011c57806311edc78f14610144575b5f5ffd5b3480156100eb575f5ffd5b50610106600480360381019061010191906115c9565b610372565b604051610113919061160e565b60405180910390f35b348015610127575f5ffd5b50610142600480360381019061013d919061165a565b6103eb565b005b34801561014f575f5ffd5b5061016a6004803603810190610165919061165a565b6107f7565b005b348015610177575f5ffd5b50610192600480360381019061018d919061165a565b61092f565b60405161019f9190611694565b60405180910390f35b3480156101b3575f5ffd5b506101ce60048036038101906101c99190611707565b61094b565b005b3480156101db575f5ffd5b506101f660048036038101906101f19190611707565b61096d565b005b610212600480360381019061020d9190611778565b6109e8565b005b34801561021f575f5ffd5b50610228610c78565b6040516102359190611694565b60405180910390f35b348015610249575f5ffd5b50610264600480360381019061025f9190611707565b610c9c565b604051610271919061160e565b60405180910390f35b348015610285575f5ffd5b5061028e610cff565b60405161029b9190611694565b60405180910390f35b3480156102af575f5ffd5b506102ca60048036038101906102c59190611707565b610d05565b005b3480156102d7575f5ffd5b506102f260048036038101906102ed91906117b6565b610d27565b6040516102ff9190611694565b60405180910390f35b348015610313575f5ffd5b5061031c610fd6565b6040516103299190611694565b60405180910390f35b34801561033d575f5ffd5b506103586004803603810190610353919061165a565b610ffa565b604051610369959493929190611838565b60405180910390f35b5f7f7965db0b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103e457506103e382611083565b5b9050919050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa610415816110ec565b61041d611100565b5f60015f8481526020019081526020015f209050806003015f9054906101000a900460ff16610478576040517f8d0fc1dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060030160019054906101000a900460ff16156104c1576040517fae89945400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160030160016101000a81548160ff0219169083151502179055505f815f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f826001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f836002015490505f845f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f846001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f84600201819055505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106d1578047101561062c576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8373ffffffffffffffffffffffffffffffffffffffff1682604051610651906118b6565b5f6040518083038185875af1925050503d805f811461068b576040519150601f19603f3d011682016040523d82523d5f602084013e610690565b606091505b50509050806106cb576040517fb12d13eb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506107ae565b808273ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161070b91906118ca565b602060405180830381865afa158015610726573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061074a91906118f7565b1015610782576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6107ad83828473ffffffffffffffffffffffffffffffffffffffff166111229092919063ffffffff16565b5b857f150e5422471a0e0b0bf81bb0c466ec4b78850d2feeea6955c7e5eb33468a9c9c60016040516107df919061160e565b60405180910390a2505050506107f3611175565b5050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa610821816110ec565b610829611100565b5f60015f8481526020019081526020015f209050806003015f9054906101000a900460ff16610884576040517f8d0fc1dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060030160019054906101000a900460ff16156108cd576040517fae89945400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160030160016101000a81548160ff021916908315150217905550827f150e5422471a0e0b0bf81bb0c466ec4b78850d2feeea6955c7e5eb33468a9c9c5f60405161091a919061160e565b60405180910390a25061092b611175565b5050565b5f5f5f8381526020019081526020015f20600101549050919050565b6109548261092f565b61095d816110ec565b610967838361118f565b50505050565b610975611278565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146109d9576040517f6697b23200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109e3828261127f565b505050565b6109f0611100565b5f8103610a29576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8190505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a9e57813414610a99576040517fbc6f88c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c06565b5f3414610ad7576040517fa57ec87300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610b1191906118ca565b602060405180830381865afa158015610b2c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5091906118f7565b9050610b7f3330858773ffffffffffffffffffffffffffffffffffffffff16611368909392919063ffffffff16565b808473ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610bb991906118ca565b602060405180830381865afa158015610bd4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bf891906118f7565b610c02919061194f565b9150505b8273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a783604051610c639190611982565b60405180910390a350610c74611175565b5050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa81565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f5f1b81565b610d0e8261092f565b610d17816110ec565b610d21838361127f565b50505050565b5f7f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f3610d52816110ec565b610d5a611100565b5f8403610d93576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b463087878787604051602001610dae9695949392919061199b565b60405160208183030381529060405280519060200120915060015f8381526020019081526020015f206003015f9054906101000a900460ff1615610e1e576040517f157c65e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040518060a001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018581526020016001151581526020015f151581525060015f8481526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550604082015181600201556060820151816003015f6101000a81548160ff02191690831515021790555060808201518160030160016101000a81548160ff0219169083151502179055509050508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16837f669c87d38156449c65caf07041b1568372d50fc03f2cc46add1d68cebc2eb9898787604051610fbd9291906119fa565b60405180910390a4610fcd611175565b50949350505050565b7f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f381565b6001602052805f5260405f205f91509050805f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806002015490806003015f9054906101000a900460ff16908060030160019054906101000a900460ff16905085565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b6110fd816110f8611278565b6113bd565b50565b61110861140e565b600261111a61111561144f565b611478565b5f0181905550565b61112f8383836001611481565b61117057826040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161116791906118ca565b60405180910390fd5b505050565b600161118761118261144f565b611478565b5f0181905550565b5f61119a8383610c9c565b61126e5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061120b611278565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019050611272565b5f90505b92915050565b5f33905090565b5f61128a8383610c9c565b1561135e575f5f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055506112fb611278565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a460019050611362565b5f90505b92915050565b6113768484848460016114e3565b6113b757836040517f5274afe70000000000000000000000000000000000000000000000000000000081526004016113ae91906118ca565b60405180910390fd5b50505050565b6113c78282610c9c565b61140a5780826040517fe2517d3f000000000000000000000000000000000000000000000000000000008152600401611401929190611a21565b60405180910390fd5b5050565b611416611554565b1561144d576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b5f5f63a9059cbb60e01b9050604051815f525f1960601c86166004528460245260205f60445f5f8b5af1925060015f511483166114d55783831516156114c9573d5f823e3d81fd5b5f873b113d1516831692505b806040525050949350505050565b5f5f6323b872dd60e01b9050604051815f525f1960601c87166004525f1960601c86166024528460445260205f60645f5f8c5af1925060015f51148316611541578383151615611535573d5f823e3d81fd5b5f883b113d1516831692505b806040525f606052505095945050505050565b5f600261156761156261144f565b611478565b5f015414905090565b5f5ffd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6115a881611574565b81146115b2575f5ffd5b50565b5f813590506115c38161159f565b92915050565b5f602082840312156115de576115dd611570565b5b5f6115eb848285016115b5565b91505092915050565b5f8115159050919050565b611608816115f4565b82525050565b5f6020820190506116215f8301846115ff565b92915050565b5f819050919050565b61163981611627565b8114611643575f5ffd5b50565b5f8135905061165481611630565b92915050565b5f6020828403121561166f5761166e611570565b5b5f61167c84828501611646565b91505092915050565b61168e81611627565b82525050565b5f6020820190506116a75f830184611685565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6116d6826116ad565b9050919050565b6116e6816116cc565b81146116f0575f5ffd5b50565b5f81359050611701816116dd565b92915050565b5f5f6040838503121561171d5761171c611570565b5b5f61172a85828601611646565b925050602061173b858286016116f3565b9150509250929050565b5f819050919050565b61175781611745565b8114611761575f5ffd5b50565b5f813590506117728161174e565b92915050565b5f5f6040838503121561178e5761178d611570565b5b5f61179b858286016116f3565b92505060206117ac85828601611764565b9150509250929050565b5f5f5f5f608085870312156117ce576117cd611570565b5b5f6117db878288016116f3565b94505060206117ec878288016116f3565b93505060406117fd87828801611764565b925050606061180e87828801611764565b91505092959194509250565b611823816116cc565b82525050565b61183281611745565b82525050565b5f60a08201905061184b5f83018861181a565b611858602083018761181a565b6118656040830186611829565b61187260608301856115ff565b61187f60808301846115ff565b9695505050505050565b5f81905092915050565b50565b5f6118a15f83611889565b91506118ac82611893565b5f82019050919050565b5f6118c082611896565b9150819050919050565b5f6020820190506118dd5f83018461181a565b92915050565b5f815190506118f18161174e565b92915050565b5f6020828403121561190c5761190b611570565b5b5f611919848285016118e3565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61195982611745565b915061196483611745565b925082820390508181111561197c5761197b611922565b5b92915050565b5f6020820190506119955f830184611829565b92915050565b5f60c0820190506119ae5f830189611829565b6119bb602083018861181a565b6119c8604083018761181a565b6119d5606083018661181a565b6119e26080830185611829565b6119ef60a0830184611829565b979650505050505050565b5f604082019050611a0d5f830185611829565b611a1a6020830184611829565b9392505050565b5f604082019050611a345f83018561181a565b611a416020830184611685565b939250505056fea2646970667358221220675434c4bbddcb90fe2fd6368a14981a2f3c3f613b72899e2626a1e12ecf1b0664736f6c634300081e0033 diff --git a/chain/gen.go b/chain/gen.go deleted file mode 100644 index 185843a..0000000 --- a/chain/gen.go +++ /dev/null @@ -1,4 +0,0 @@ -package chain - -//go:generate sh -c "jq .abi ../contracts/evm/out/IWithdraw.sol/IWithdraw.json > IWithdraw.abi && abigen --abi IWithdraw.abi --pkg chain --type IWithdraw --out iwithdraw.go" -//go:generate sh -c "jq .abi ../contracts/evm/out/SimpleCustody.sol/SimpleCustody.json > SimpleCustody.abi && jq -r .bytecode.object ../contracts/evm/out/SimpleCustody.sol/SimpleCustody.json > SimpleCustody.bin && abigen --abi SimpleCustody.abi --bin SimpleCustody.bin --pkg chain --type SimpleCustody --out simple_custody.go" diff --git a/chain/listener.go b/chain/listener.go deleted file mode 100644 index b6e942b..0000000 --- a/chain/listener.go +++ /dev/null @@ -1,199 +0,0 @@ -package chain - -import ( - "context" - "fmt" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/core/types" - - nw "github.com/layer-3/nitewatch" -) - -// HeadSubscriber abstracts the ability to subscribe to new block headers. -// *ethclient.Client satisfies this interface. -type HeadSubscriber interface { - SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) -} - -// Listener handles monitoring the blockchain for events from the IWithdraw contract. -type Listener struct { - headSub HeadSubscriber - withdraw *IWithdraw - confirmations uint64 -} - -// NewListener creates a new Listener instance. -// headSub: a client supporting header subscriptions (e.g. *ethclient.Client via WebSocket) -// withdraw: bound IWithdraw contract instance -// confirmations: number of block confirmations required before an event is considered final -func NewListener(headSub HeadSubscriber, withdraw *IWithdraw, confirmations uint64) *Listener { - return &Listener{ - headSub: headSub, - withdraw: withdraw, - confirmations: confirmations, - } -} - -// Compile-time check that Listener implements nw.EventListener. -var _ nw.EventListener = (*Listener)(nil) - -// WatchWithdrawStarted subscribes to WithdrawStarted events and sends confirmed domain events to the sink channel. -func (l *Listener) WatchWithdrawStarted(ctx context.Context, sink chan<- *nw.WithdrawStartedEvent) error { - raw := make(chan *IWithdrawWithdrawStarted) - - errCh := make(chan error, 1) - go func() { - errCh <- watchWithConfirmations(ctx, raw, l.confirmations, - func(rawSink chan<- *IWithdrawWithdrawStarted) (ethereum.Subscription, error) { - return l.withdraw.WatchWithdrawStarted(&bind.WatchOpts{Context: ctx}, rawSink, nil, nil, nil) - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - return l.headSub.SubscribeNewHead(ctx, ch) - }, - func(e *IWithdrawWithdrawStarted) types.Log { return e.Raw }, - ) - }() - - defer close(sink) - for ev := range raw { - select { - case sink <- &nw.WithdrawStartedEvent{ - WithdrawalID: ev.WithdrawalId, - User: ev.User, - Token: ev.Token, - Amount: ev.Amount, - Nonce: ev.Nonce, - BlockNumber: ev.Raw.BlockNumber, - TxHash: ev.Raw.TxHash, - }: - case <-ctx.Done(): - return ctx.Err() - } - } - - return <-errCh -} - -// WatchWithdrawFinalized subscribes to WithdrawFinalized events and sends confirmed domain events to the sink channel. -func (l *Listener) WatchWithdrawFinalized(ctx context.Context, sink chan<- *nw.WithdrawFinalizedEvent) error { - raw := make(chan *IWithdrawWithdrawFinalized) - - errCh := make(chan error, 1) - go func() { - errCh <- watchWithConfirmations(ctx, raw, l.confirmations, - func(rawSink chan<- *IWithdrawWithdrawFinalized) (ethereum.Subscription, error) { - return l.withdraw.WatchWithdrawFinalized(&bind.WatchOpts{Context: ctx}, rawSink, nil) - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - return l.headSub.SubscribeNewHead(ctx, ch) - }, - func(e *IWithdrawWithdrawFinalized) types.Log { return e.Raw }, - ) - }() - - defer close(sink) - for ev := range raw { - select { - case sink <- &nw.WithdrawFinalizedEvent{ - WithdrawalID: ev.WithdrawalId, - Success: ev.Success, - BlockNumber: ev.Raw.BlockNumber, - TxHash: ev.Raw.TxHash, - }: - case <-ctx.Done(): - return ctx.Err() - } - } - - return <-errCh -} - -// watchWithConfirmations is the generic confirmation-tracking event watcher. -// It buffers events until they reach the required block depth before emitting them to sink. -// If confirmations is 0, events are emitted immediately. -func watchWithConfirmations[E any]( - ctx context.Context, - sink chan<- E, - confirmations uint64, - subscribe func(chan<- E) (ethereum.Subscription, error), - subscribeHead func(chan<- *types.Header) (ethereum.Subscription, error), - getRaw func(E) types.Log, -) error { - defer close(sink) - - rawSink := make(chan E) - sub, err := subscribe(rawSink) - if err != nil { - return fmt.Errorf("failed to subscribe to events: %w", err) - } - defer sub.Unsubscribe() - - headers := make(chan *types.Header) - headSub, err := subscribeHead(headers) - if err != nil { - return fmt.Errorf("failed to subscribe to new heads: %w", err) - } - defer headSub.Unsubscribe() - - type pendingEvent struct { - event E - blockNumber uint64 - } - var pending []pendingEvent - - for { - select { - case <-ctx.Done(): - return ctx.Err() - case err := <-sub.Err(): - return fmt.Errorf("event subscription error: %w", err) - case err := <-headSub.Err(): - return fmt.Errorf("header subscription error: %w", err) - case ev := <-rawSink: - raw := getRaw(ev) - if raw.Removed { - n := 0 - for _, p := range pending { - pRaw := getRaw(p.event) - if pRaw.TxHash != raw.TxHash || pRaw.Index != raw.Index { - pending[n] = p - n++ - } - } - pending = pending[:n] - } else if confirmations == 0 { - select { - case sink <- ev: - case <-ctx.Done(): - return ctx.Err() - } - } else { - pending = append(pending, pendingEvent{ - event: ev, - blockNumber: raw.BlockNumber, - }) - } - case head := <-headers: - if confirmations == 0 { - continue - } - currentBlock := head.Number.Uint64() - n := 0 - for _, p := range pending { - if currentBlock+1 >= p.blockNumber+confirmations { - select { - case sink <- p.event: - case <-ctx.Done(): - return ctx.Err() - } - } else { - pending[n] = p - n++ - } - } - pending = pending[:n] - } - } -} diff --git a/chain/listener_test.go b/chain/listener_test.go deleted file mode 100644 index 80b33f6..0000000 --- a/chain/listener_test.go +++ /dev/null @@ -1,399 +0,0 @@ -package chain - -import ( - "context" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" -) - -// testEvent is a simple event type for testing the generic watchWithConfirmations. -type testEvent struct { - ID int - Raw types.Log -} - -// mockSubscription implements ethereum.Subscription for testing. -type mockSubscription struct { - errCh chan error - unsub bool - unsubC chan struct{} -} - -func newMockSubscription() *mockSubscription { - return &mockSubscription{ - errCh: make(chan error, 1), - unsubC: make(chan struct{}), - } -} - -func (m *mockSubscription) Err() <-chan error { return m.errCh } -func (m *mockSubscription) Unsubscribe() { - if !m.unsub { - m.unsub = true - close(m.unsubC) - } -} - -func TestWatchWithConfirmations_ZeroConfirmations(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - eventCh := make(chan *testEvent) - headCh := make(chan *types.Header) - sink := make(chan *testEvent, 10) - - eventSub := newMockSubscription() - headSub := newMockSubscription() - - go func() { - err := watchWithConfirmations( - ctx, sink, 0, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - go func() { - for ev := range eventCh { - rawSink <- ev - } - }() - return eventSub, nil - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - go func() { - for h := range headCh { - ch <- h - } - }() - return headSub, nil - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - _ = err - }() - - // Send an event - should be delivered immediately - eventCh <- &testEvent{ - ID: 1, - Raw: types.Log{ - BlockNumber: 100, - TxHash: common.HexToHash("0x01"), - }, - } - - select { - case ev := <-sink: - if ev.ID != 1 { - t.Fatalf("expected event ID 1, got %d", ev.ID) - } - case <-time.After(time.Second): - t.Fatal("timed out waiting for event") - } - - cancel() -} - -func TestWatchWithConfirmations_WithConfirmations(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - eventCh := make(chan *testEvent) - headCh := make(chan *types.Header) - sink := make(chan *testEvent, 10) - - eventSub := newMockSubscription() - headSub := newMockSubscription() - - go func() { - _ = watchWithConfirmations( - ctx, sink, 3, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - go func() { - for ev := range eventCh { - rawSink <- ev - } - }() - return eventSub, nil - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - go func() { - for h := range headCh { - ch <- h - } - }() - return headSub, nil - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - }() - - // Send event at block 100 - eventCh <- &testEvent{ - ID: 1, - Raw: types.Log{ - BlockNumber: 100, - TxHash: common.HexToHash("0x01"), - }, - } - - // Allow event to be buffered - time.Sleep(50 * time.Millisecond) - - // Block 101 - not enough confirmations (depth=2, need 3) - headCh <- &types.Header{Number: big.NewInt(101)} - time.Sleep(50 * time.Millisecond) - - select { - case <-sink: - t.Fatal("event should not be delivered yet") - default: - } - - // Block 102 - depth=3, should confirm (102+1 >= 100+3) - headCh <- &types.Header{Number: big.NewInt(102)} - - select { - case ev := <-sink: - if ev.ID != 1 { - t.Fatalf("expected event ID 1, got %d", ev.ID) - } - case <-time.After(time.Second): - t.Fatal("timed out waiting for confirmed event") - } - - cancel() -} - -func TestWatchWithConfirmations_ReorgRemoval(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - eventCh := make(chan *testEvent) - headCh := make(chan *types.Header) - sink := make(chan *testEvent, 10) - - eventSub := newMockSubscription() - headSub := newMockSubscription() - - go func() { - _ = watchWithConfirmations( - ctx, sink, 3, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - go func() { - for ev := range eventCh { - rawSink <- ev - } - }() - return eventSub, nil - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - go func() { - for h := range headCh { - ch <- h - } - }() - return headSub, nil - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - }() - - txHash := common.HexToHash("0x01") - - // Send event at block 100 - eventCh <- &testEvent{ - ID: 1, - Raw: types.Log{BlockNumber: 100, TxHash: txHash, Index: 0}, - } - - time.Sleep(50 * time.Millisecond) - - // Reorg: same event is removed - eventCh <- &testEvent{ - ID: 1, - Raw: types.Log{BlockNumber: 100, TxHash: txHash, Index: 0, Removed: true}, - } - - time.Sleep(50 * time.Millisecond) - - // Even with enough blocks, the removed event should not be delivered - headCh <- &types.Header{Number: big.NewInt(105)} - time.Sleep(50 * time.Millisecond) - - select { - case <-sink: - t.Fatal("removed event should not be delivered") - default: - } - - cancel() -} - -func TestWatchWithConfirmations_MultipleEvents(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - eventCh := make(chan *testEvent) - headCh := make(chan *types.Header) - sink := make(chan *testEvent, 10) - - eventSub := newMockSubscription() - headSub := newMockSubscription() - - go func() { - _ = watchWithConfirmations( - ctx, sink, 2, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - go func() { - for ev := range eventCh { - rawSink <- ev - } - }() - return eventSub, nil - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - go func() { - for h := range headCh { - ch <- h - } - }() - return headSub, nil - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - }() - - // Events at different blocks - eventCh <- &testEvent{ID: 1, Raw: types.Log{BlockNumber: 100, TxHash: common.HexToHash("0x01")}} - eventCh <- &testEvent{ID: 2, Raw: types.Log{BlockNumber: 101, TxHash: common.HexToHash("0x02")}} - eventCh <- &testEvent{ID: 3, Raw: types.Log{BlockNumber: 102, TxHash: common.HexToHash("0x03")}} - - time.Sleep(50 * time.Millisecond) - - // Block 101: confirms event 1 (101+1 >= 100+2), not event 2 or 3 - headCh <- &types.Header{Number: big.NewInt(101)} - - select { - case ev := <-sink: - if ev.ID != 1 { - t.Fatalf("expected event 1 first, got %d", ev.ID) - } - case <-time.After(time.Second): - t.Fatal("timed out") - } - - // Block 103: confirms events 2 and 3 - headCh <- &types.Header{Number: big.NewInt(103)} - - received := map[int]bool{} - for i := 0; i < 2; i++ { - select { - case ev := <-sink: - received[ev.ID] = true - case <-time.After(time.Second): - t.Fatal("timed out") - } - } - - if !received[2] || !received[3] { - t.Fatalf("expected events 2 and 3, got %v", received) - } - - cancel() -} - -func TestWatchWithConfirmations_ContextCancellation(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - - sink := make(chan *testEvent, 10) - eventSub := newMockSubscription() - headSub := newMockSubscription() - - errCh := make(chan error, 1) - go func() { - errCh <- watchWithConfirmations( - ctx, sink, 0, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - return eventSub, nil - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - return headSub, nil - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - }() - - cancel() - - select { - case err := <-errCh: - if err != context.Canceled { - t.Fatalf("expected context.Canceled, got: %v", err) - } - case <-time.After(time.Second): - t.Fatal("timed out waiting for cancellation") - } -} - -func TestWatchWithConfirmations_SubscribeEventError(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - sink := make(chan *testEvent, 10) - - errCh := make(chan error, 1) - go func() { - errCh <- watchWithConfirmations( - ctx, sink, 0, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - return nil, context.DeadlineExceeded - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - return newMockSubscription(), nil - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - }() - - select { - case err := <-errCh: - if err == nil { - t.Fatal("expected error") - } - case <-time.After(time.Second): - t.Fatal("timed out") - } -} - -func TestWatchWithConfirmations_SubscribeHeadError(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - sink := make(chan *testEvent, 10) - eventSub := newMockSubscription() - - errCh := make(chan error, 1) - go func() { - errCh <- watchWithConfirmations( - ctx, sink, 0, - func(rawSink chan<- *testEvent) (ethereum.Subscription, error) { - return eventSub, nil - }, - func(ch chan<- *types.Header) (ethereum.Subscription, error) { - return nil, context.DeadlineExceeded - }, - func(e *testEvent) types.Log { return e.Raw }, - ) - }() - - select { - case err := <-errCh: - if err == nil { - t.Fatal("expected error") - } - case <-time.After(time.Second): - t.Fatal("timed out") - } -} diff --git a/chain/simulated_backend_test.go b/chain/simulated_backend_test.go deleted file mode 100644 index 6910aef..0000000 --- a/chain/simulated_backend_test.go +++ /dev/null @@ -1,168 +0,0 @@ -package chain - -import ( - "context" - "crypto/ecdsa" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" -) - -func TestSimpleCustodyFlow(t *testing.T) { - // 1. Setup Accounts - // We need 4 accounts: Admin, NeoDAX, Nitewatch, User - keys := make([]*ecdsa.PrivateKey, 4) - addrs := make([]common.Address, 4) - auths := make([]*bind.TransactOpts, 4) - - alloc := make(types.GenesisAlloc) - balance := new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18)) // 1000 ETH - - for i := 0; i < 4; i++ { - key, err := crypto.GenerateKey() - if err != nil { - t.Fatalf("Failed to generate key: %v", err) - } - keys[i] = key - addrs[i] = crypto.PubkeyToAddress(key.PublicKey) - - auth, err := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - if err != nil { - t.Fatalf("Failed to create auth: %v", err) - } - auths[i] = auth - - alloc[addrs[i]] = types.Account{Balance: balance} - } - - adminAuth := auths[0] - neodaxAuth := auths[1] - nitewatchAuth := auths[2] - userAuth := auths[3] - - adminAddr := addrs[0] - neodaxAddr := addrs[1] - nitewatchAddr := addrs[2] - userAddr := addrs[3] - - // 2. Setup Simulated Backend - sim := backends.NewSimulatedBackend(alloc, 8000000) - defer sim.Close() - - // 3. Deploy Contract - custodyAddr, _, custody, err := DeploySimpleCustody(adminAuth, sim, adminAddr, neodaxAddr, nitewatchAddr) - if err != nil { - t.Fatalf("Failed to deploy SimpleCustody: %v", err) - } - sim.Commit() - - // 4. Test Deposit Flow - t.Run("Deposit ETH", func(t *testing.T) { - depositAmount := big.NewInt(1e18) // 1 ETH - - // User deposits - // Note: Deposit requires value to be sent. `userAuth` needs to be updated with value. - userAuth.Value = depositAmount - tx, err := custody.Deposit(userAuth, common.Address{}, depositAmount) - userAuth.Value = nil // Reset - if err != nil { - t.Fatalf("Failed to deposit: %v", err) - } - sim.Commit() - - receipt, err := sim.TransactionReceipt(context.Background(), tx.Hash()) - if err != nil { - t.Fatalf("Failed to get receipt: %v", err) - } - if receipt.Status != types.ReceiptStatusSuccessful { - t.Fatal("Deposit transaction failed") - } - - // Check contract balance - contractBalance, err := sim.BalanceAt(context.Background(), custodyAddr, nil) - if err != nil { - t.Fatalf("Failed to get contract balance: %v", err) - } - if contractBalance.Cmp(depositAmount) != 0 { - t.Errorf("Expected contract balance %v, got %v", depositAmount, contractBalance) - } - }) - - // 5. Test Withdrawal Flow - t.Run("Withdraw ETH", func(t *testing.T) { - withdrawAmount := big.NewInt(5e17) // 0.5 ETH - nonce := big.NewInt(1) - - // Record User balance before - userBalBefore, err := sim.BalanceAt(context.Background(), userAddr, nil) - if err != nil { - t.Fatalf("Failed to get user balance: %v", err) - } - - // A. Start Withdraw (NeoDAX) - tx, err := custody.StartWithdraw(neodaxAuth, userAddr, common.Address{}, withdrawAmount, nonce) - if err != nil { - t.Fatalf("Failed to start withdraw: %v", err) - } - sim.Commit() - - receipt, err := sim.TransactionReceipt(context.Background(), tx.Hash()) - if err != nil { - t.Fatalf("Failed to get start withdraw receipt: %v", err) - } - if receipt.Status != types.ReceiptStatusSuccessful { - t.Fatal("StartWithdraw transaction failed") - } - - // Parse event to get withdrawalId - // In a real scenario we'd parse logs. Here we can iterate logs. - var withdrawalId [32]byte - found := false - for _, log := range receipt.Logs { - event, err := custody.ParseWithdrawStarted(*log) - if err == nil { - withdrawalId = event.WithdrawalId - found = true - break - } - } - if !found { - t.Fatal("WithdrawStarted event not found") - } - - // B. Finalize Withdraw (Nitewatch) - tx, err = custody.FinalizeWithdraw(nitewatchAuth, withdrawalId) - if err != nil { - t.Fatalf("Failed to finalize withdraw: %v", err) - } - sim.Commit() - - receipt, err = sim.TransactionReceipt(context.Background(), tx.Hash()) - if err != nil { - t.Fatalf("Failed to get finalize withdraw receipt: %v", err) - } - if receipt.Status != types.ReceiptStatusSuccessful { - t.Fatal("FinalizeWithdraw transaction failed") - } - - // Check User balance after - userBalAfter, err := sim.BalanceAt(context.Background(), userAddr, nil) - if err != nil { - t.Fatalf("Failed to get user balance: %v", err) - } - - expectedBal := new(big.Int).Add(userBalBefore, withdrawAmount) - // Note: user pays gas for nothing here? No, user doesn't call anything. - // User receives funds. Gas is paid by Nitewatch. - // So user balance should be exactly previous + withdrawn amount. - - if userBalAfter.Cmp(expectedBal) != 0 { - t.Errorf("Expected user balance %v, got %v", expectedBal, userBalAfter) - } - }) -} diff --git a/cmd/example/usage.go b/cmd/example/usage.go deleted file mode 100644 index c086cbe..0000000 --- a/cmd/example/usage.go +++ /dev/null @@ -1,182 +0,0 @@ -package main - -import ( - "context" - "crypto/ecdsa" - "fmt" - "log" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/layer-3/nitewatch/chain" -) - -func main() { - // 1. Setup Accounts - // We generate keys for Admin, NeoDAX, Nitewatch, and a User. - keys := make([]*ecdsa.PrivateKey, 4) - addrs := make([]common.Address, 4) - auths := make([]*bind.TransactOpts, 4) - - alloc := make(types.GenesisAlloc) - balance := new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18)) // 1000 ETH - - fmt.Println("Setting up accounts...") - for i := 0; i < 4; i++ { - key, err := crypto.GenerateKey() - if err != nil { - log.Fatalf("Failed to generate key: %v", err) - } - keys[i] = key - addrs[i] = crypto.PubkeyToAddress(key.PublicKey) - - auth, err := bind.NewKeyedTransactorWithChainID(key, big.NewInt(1337)) - if err != nil { - log.Fatalf("Failed to create auth: %v", err) - } - auths[i] = auth - - alloc[addrs[i]] = types.Account{Balance: balance} - } - - adminAuth := auths[0] - neodaxAuth := auths[1] - nitewatchAuth := auths[2] - userAuth := auths[3] - - // 2. Setup Simulated Backend - fmt.Println("Initializing simulated backend...") - sim := backends.NewSimulatedBackend(alloc, 8000000) - defer sim.Close() - - // 3. Deploy Contract - fmt.Println("Deploying SimpleCustody contract...") - custodyAddr, _, custody, err := chain.DeploySimpleCustody(adminAuth, sim, addrs[0], addrs[1], addrs[2]) - if err != nil { - log.Fatalf("Failed to deploy SimpleCustody: %v", err) - } - sim.Commit() - fmt.Printf("Contract deployed at: %s\n", custodyAddr.Hex()) - - // 4. Start Event Listeners (Background) - go func() { - // Watch for Deposits - depositCh := make(chan *chain.SimpleCustodyDeposited) - depositSub, err := custody.WatchDeposited(&bind.WatchOpts{}, depositCh, nil, nil) - if err != nil { - log.Printf("Failed to watch deposits: %v", err) - return - } - defer depositSub.Unsubscribe() - - // Watch for Withdrawal Requests - withdrawStartedCh := make(chan *chain.SimpleCustodyWithdrawStarted) - withdrawStartedSub, err := custody.WatchWithdrawStarted(&bind.WatchOpts{}, withdrawStartedCh, nil, nil, nil) - if err != nil { - log.Printf("Failed to watch withdraw started: %v", err) - return - } - defer withdrawStartedSub.Unsubscribe() - - // Watch for Finalized Withdrawals - withdrawFinalizedCh := make(chan *chain.SimpleCustodyWithdrawFinalized) - withdrawFinalizedSub, err := custody.WatchWithdrawFinalized(&bind.WatchOpts{}, withdrawFinalizedCh, nil) - if err != nil { - log.Printf("Failed to watch withdraw finalized: %v", err) - return - } - defer withdrawFinalizedSub.Unsubscribe() - - fmt.Println("Listening for events...") - - for { - select { - case ev := <-depositCh: - fmt.Printf("[EVENT] Deposited: User=%s Token=%s Amount=%s\n", ev.User.Hex(), ev.Token.Hex(), ev.Amount.String()) - case ev := <-withdrawStartedCh: - fmt.Printf("[EVENT] WithdrawStarted: ID=%x User=%s Amount=%s\n", ev.WithdrawalId, ev.User.Hex(), ev.Amount.String()) - case ev := <-withdrawFinalizedCh: - fmt.Printf("[EVENT] WithdrawFinalized: ID=%x Success=%v\n", ev.WithdrawalId, ev.Success) - case err := <-depositSub.Err(): - log.Printf("Deposit subscription error: %v", err) - return - case err := <-withdrawStartedSub.Err(): - log.Printf("WithdrawStarted subscription error: %v", err) - return - case err := <-withdrawFinalizedSub.Err(): - log.Printf("WithdrawFinalized subscription error: %v", err) - return - } - } - }() - - // Allow listeners to subscribe - time.Sleep(100 * time.Millisecond) - - // 5. Execute Deposit - fmt.Println("\n--- Executing Deposit ---") - depositAmount := big.NewInt(1e18) // 1 ETH - userAuth.Value = depositAmount - tx, err := custody.Deposit(userAuth, common.Address{}, depositAmount) - userAuth.Value = nil // Reset value - if err != nil { - log.Fatalf("Failed to deposit: %v", err) - } - fmt.Printf("Deposit transaction sent: %s\n", tx.Hash().Hex()) - sim.Commit() - - // Wait a bit for event processing - time.Sleep(200 * time.Millisecond) - - // 6. Execute Withdrawal - fmt.Println("\n--- Executing Withdrawal ---") - withdrawAmount := big.NewInt(5e17) // 0.5 ETH - nonce := big.NewInt(1) - - // NeoDAX starts withdrawal - fmt.Println("NeoDAX initiating withdrawal...") - tx, err = custody.StartWithdraw(neodaxAuth, addrs[3], common.Address{}, withdrawAmount, nonce) - if err != nil { - log.Fatalf("Failed to start withdraw: %v", err) - } - sim.Commit() - - receipt, err := sim.TransactionReceipt(context.Background(), tx.Hash()) - if err != nil { - log.Fatalf("Failed to get receipt: %v", err) - } - - // Find the withdrawal ID from the receipt logs to pass to finalize - // In a real app, the Nitewatch daemon would pick this up from the event stream. - // Here we parse it manually for the "Nitewatch" actor simulation. - var withdrawalId [32]byte - for _, log := range receipt.Logs { - event, err := custody.ParseWithdrawStarted(*log) - if err == nil { - withdrawalId = event.WithdrawalId - break - } - } - fmt.Printf("Withdrawal ID: %x\n", withdrawalId) - - // Wait for event listener to print "WithdrawStarted" - time.Sleep(200 * time.Millisecond) - - // Nitewatch finalizes withdrawal - fmt.Println("Nitewatch finalizing withdrawal...") - tx, err = custody.FinalizeWithdraw(nitewatchAuth, withdrawalId) - if err != nil { - log.Fatalf("Failed to finalize withdraw: %v", err) - } - sim.Commit() - - // Wait for event listener to print "WithdrawFinalized" - time.Sleep(200 * time.Millisecond) - - fmt.Println("\n--- Demo Complete ---") -} diff --git a/cmd/nitewatch/config.yaml b/cmd/nitewatch/config.yaml new file mode 100644 index 0000000..68e2ff2 --- /dev/null +++ b/cmd/nitewatch/config.yaml @@ -0,0 +1,19 @@ +blockchain: + rpc_url: "${NITEWATCH_RPC_URL}" + contract_address: "${NITEWATCH_CONTRACT_ADDRESS}" + private_key: "${NITEWATCH_PRIVATE_KEY}" + +limits: + # Native ETH (zero address) + "0x0000000000000000000000000000000000000000": + hourly: "1000000000000000000" # 1 ETH + daily: "10000000000000000000" # 10 ETH + +# per_user_overrides: +# "0xUserAddress...": +# "0x0000000000000000000000000000000000000000": +# hourly: "5000000000000000000" +# daily: "50000000000000000000" + +listen_addr: ":8080" +db_path: "nitewatch.db" diff --git a/cmd/nitewatch/limits.yaml b/cmd/nitewatch/limits.yaml deleted file mode 100644 index d547c80..0000000 --- a/cmd/nitewatch/limits.yaml +++ /dev/null @@ -1,5 +0,0 @@ -limits: - # Example Token Address (Replace with actual deployment address) - "0x5FbDB2315678afecb367f032d93F642f64180aa3": - hourly: "1000000000000000000" # 1 ETH - daily: "10000000000000000000" # 10 ETH diff --git a/cmd/nitewatch/main.go b/cmd/nitewatch/main.go index f80fef3..ea8a281 100644 --- a/cmd/nitewatch/main.go +++ b/cmd/nitewatch/main.go @@ -1,181 +1,39 @@ package main import ( - "context" - _ "embed" - "flag" - "log" + "fmt" + "log/slog" "os" - "os/signal" - "syscall" - "time" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethclient" - "gopkg.in/yaml.v3" - "gorm.io/driver/sqlite" - "gorm.io/gorm" - - nw "github.com/layer-3/nitewatch" - "github.com/layer-3/nitewatch/chain" - "github.com/layer-3/nitewatch/core" - "github.com/layer-3/nitewatch/store" + "github.com/layer-3/nitewatch/config" + "github.com/layer-3/nitewatch/service" ) -//go:embed limits.yaml -var limitsConfig []byte - func main() { - rpcURL := flag.String("rpc", "ws://127.0.0.1:8545", "Ethereum RPC URL (WebSocket required)") - contractAddr := flag.String("contract", "", "IWithdraw contract address") - privateKeyHex := flag.String("key", "", "Private key for finalizing withdrawals (hex)") - confirmations := flag.Uint64("confirmations", 12, "Number of block confirmations to wait") - dbPath := flag.String("db", "nitewatch.db", "Path to SQLite database") - flag.Parse() - - if *contractAddr == "" || *privateKeyHex == "" { - log.Fatal("Contract address and private key are required") - } - - // 1. Initialize Store - gormDB, err := gorm.Open(sqlite.Open(*dbPath), &gorm.Config{}) - if err != nil { - log.Fatalf("Failed to open database: %v", err) - } - - db, err := store.NewAdapter(gormDB) - if err != nil { - log.Fatalf("Failed to initialize database: %v", err) - } - - // 2. Load Configuration and Initialize Checker - var cfg core.Config - if err := yaml.Unmarshal(limitsConfig, &cfg); err != nil { - log.Fatalf("Failed to parse embedded limits.yaml: %v", err) - } - - checker, err := core.NewChecker(cfg, db) - if err != nil { - log.Fatalf("Failed to initialize checker: %v", err) - } - - // 3. Connect to Ethereum - client, err := ethclient.Dial(*rpcURL) - if err != nil { - log.Fatalf("Failed to connect to RPC: %v", err) + if len(os.Args) < 2 || os.Args[1] != "worker" { + fmt.Fprintln(os.Stderr, "usage: nitewatch worker") + os.Exit(1) } - defer client.Close() - chainID, err := client.ChainID(context.Background()) - if err != nil { - log.Fatalf("Failed to get chain ID: %v", err) + configPath := os.Getenv("NITEWATCH_CONFIG") + if configPath == "" { + configPath = "config.yaml" } - // 4. Setup Signer - key, err := crypto.HexToECDSA(*privateKeyHex) + conf, err := config.Load(configPath) if err != nil { - log.Fatalf("Failed to parse private key: %v", err) + slog.Error("Failed to load configuration", "error", err) + os.Exit(1) } - auth, err := bind.NewKeyedTransactorWithChainID(key, chainID) + svc, err := service.New(*conf) if err != nil { - log.Fatalf("Failed to create transactor: %v", err) + slog.Error("Failed to create service", "error", err) + os.Exit(1) } - // 5. Setup Contract Binding - addr := common.HexToAddress(*contractAddr) - custodyContract, err := chain.NewIWithdraw(addr, client) - if err != nil { - log.Fatalf("Failed to bind contract: %v", err) - } - - // 6. Setup Listener - listener := chain.NewListener(client, custodyContract, *confirmations) - - // 7. Start Watching - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - withdrawals := make(chan *nw.WithdrawStartedEvent) - - // Handle shutdown - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigCh - log.Println("Shutting down...") - cancel() - }() - - go func() { - log.Println("Listening for WithdrawStarted events...") - if err := listener.WatchWithdrawStarted(ctx, withdrawals); err != nil { - if ctx.Err() == nil { - log.Printf("WatchWithdrawStarted error: %v", err) - } - } - }() - - // 8. Process Loop - for event := range withdrawals { - log.Printf("New withdrawal request: ID=%x User=%s Token=%s Amount=%s", - event.WithdrawalID, event.User.Hex(), event.Token.Hex(), event.Amount) - - // Check Limits - if err := checker.Check(event.Token, event.Amount); err != nil { - log.Printf("Withdrawal %x blocked by policy: %v. Rejecting on-chain...", event.WithdrawalID, err) - - txAuth := *auth - txAuth.Context = ctx - tx, err := custodyContract.RejectWithdraw(&txAuth, event.WithdrawalID) - if err != nil { - log.Printf("Failed to reject withdrawal %x: %v", event.WithdrawalID, err) - } else { - log.Printf("Sent reject tx: %s for withdrawal %x", tx.Hash().Hex(), event.WithdrawalID) - bind.WaitMined(ctx, client, tx) - } - continue - } - - // Finalize - txAuth := *auth - txAuth.Context = ctx - - tx, err := custodyContract.FinalizeWithdraw(&txAuth, event.WithdrawalID) - if err != nil { - log.Printf("Failed to finalize withdrawal %x: %v", event.WithdrawalID, err) - continue - } - - log.Printf("Sent finalize tx: %s for withdrawal %x", tx.Hash().Hex(), event.WithdrawalID) - - receipt, err := bind.WaitMined(ctx, client, tx) - if err != nil { - log.Printf("Transaction mining failed: %v", err) - continue - } - - if receipt.Status == 1 { - log.Printf("Withdrawal %x finalized successfully on-chain.", event.WithdrawalID) - - // Record usage in DB - record := &nw.Withdrawal{ - WithdrawalID: event.WithdrawalID, - User: event.User, - Token: event.Token, - Amount: event.Amount, - BlockNumber: receipt.BlockNumber.Uint64(), - TxHash: tx.Hash(), - Timestamp: time.Now(), - } - - if err := checker.Record(record); err != nil { - log.Printf("Failed to record withdrawal %x in DB: %v", event.WithdrawalID, err) - } - } else { - log.Printf("Withdrawal %x finalization tx failed (reverted).", event.WithdrawalID) - } + if err := svc.RunWorker(); err != nil { + slog.Error("Worker failed", "error", err) + os.Exit(1) } } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..e616249 --- /dev/null +++ b/config/config.go @@ -0,0 +1,114 @@ +package config + +import ( + "errors" + "fmt" + "math/big" + "os" + "strings" + + "github.com/ethereum/go-ethereum/common" + "gopkg.in/yaml.v3" +) + +type Config struct { + Blockchain BlockchainConfig `yaml:"blockchain"` + Limits LimitsConfig `yaml:"limits"` + PerUserOverrides map[string]LimitsConfig `yaml:"per_user_overrides"` + ListenAddr string `yaml:"listen_addr"` + DBPath string `yaml:"db_path"` +} + +type BlockchainConfig struct { + RPCURL string `yaml:"rpc_url"` + ContractAddr string `yaml:"contract_address"` + PrivateKey string `yaml:"private_key"` +} + +// LimitsConfig maps token contract addresses to their withdrawal rate limits. +type LimitsConfig map[string]LimitConfig + +type LimitConfig struct { + Hourly string `yaml:"hourly"` + Daily string `yaml:"daily"` +} + +func (c Config) Validate() error { + if err := c.Blockchain.Validate(); err != nil { + return fmt.Errorf("invalid blockchain config: %w", err) + } + if len(c.Limits) == 0 { + return errors.New("at least one token limit must be configured") + } + if err := validateLimitsConfig(c.Limits, "limits"); err != nil { + return err + } + for userAddr, tokenLimits := range c.PerUserOverrides { + if !common.IsHexAddress(userAddr) { + return fmt.Errorf("invalid user address in per_user_overrides: %s", userAddr) + } + if err := validateLimitsConfig(tokenLimits, fmt.Sprintf("per_user_overrides[%s]", userAddr)); err != nil { + return err + } + } + return nil +} + +func validateLimitsConfig(lc LimitsConfig, section string) error { + for addr, lim := range lc { + if !common.IsHexAddress(addr) { + return fmt.Errorf("invalid token address in %s: %s", section, addr) + } + if lim.Hourly != "" { + if _, ok := new(big.Int).SetString(lim.Hourly, 10); !ok { + return fmt.Errorf("invalid hourly limit for %s in %s: %s", addr, section, lim.Hourly) + } + } + if lim.Daily != "" { + if _, ok := new(big.Int).SetString(lim.Daily, 10); !ok { + return fmt.Errorf("invalid daily limit for %s in %s: %s", addr, section, lim.Daily) + } + } + } + return nil +} + +func (c BlockchainConfig) Validate() error { + if c.RPCURL == "" { + return errors.New("missing blockchain RPC URL") + } + if !strings.HasPrefix(c.RPCURL, "ws://") && !strings.HasPrefix(c.RPCURL, "wss://") { + return fmt.Errorf("RPC URL must use WebSocket (ws:// or wss://), got: %s", c.RPCURL) + } + if !common.IsHexAddress(c.ContractAddr) { + return fmt.Errorf("invalid contract address: %s", c.ContractAddr) + } + if c.PrivateKey == "" { + return errors.New("missing private key") + } + return nil +} + +func Load(path string) (*Config, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("read config file: %w", err) + } + + expanded := os.ExpandEnv(string(data)) + + var cfg Config + if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil { + return nil, fmt.Errorf("parse config: %w", err) + } + + if cfg.DBPath == "" { + cfg.DBPath = "nitewatch.db" + } + + if cfg.ListenAddr == "" { + cfg.ListenAddr = ":8080" + } + + return &cfg, nil +} diff --git a/contracts/evm/lib/forge-std b/contracts/evm/lib/forge-std index 1801b05..0844d7e 160000 --- a/contracts/evm/lib/forge-std +++ b/contracts/evm/lib/forge-std @@ -1 +1 @@ -Subproject commit 1801b0541f4fda118a10798fd3486bb7051c5dd6 +Subproject commit 0844d7e1fc5e60d77b68e469bff60265f236c398 diff --git a/contracts/evm/src/MockERC20.sol b/contracts/evm/src/MockERC20.sol new file mode 100644 index 0000000..02bfe9f --- /dev/null +++ b/contracts/evm/src/MockERC20.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MockERC20 is ERC20 { + constructor() ERC20("Mock", "MCK") {} + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} diff --git a/core/checker.go b/core/checker.go deleted file mode 100644 index c9e34f2..0000000 --- a/core/checker.go +++ /dev/null @@ -1,119 +0,0 @@ -package core - -import ( - "errors" - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - nw "github.com/layer-3/nitewatch" -) - -var ( - ErrNoLimitsConfigured = errors.New("no limits configured for token") - ErrHourlyLimitExceeded = errors.New("hourly limit exceeded") - ErrDailyLimitExceeded = errors.New("daily limit exceeded") -) - -// LimitConfig defines the withdrawal constraints for a token (YAML friendly). -type LimitConfig struct { - Hourly string `yaml:"hourly"` - Daily string `yaml:"daily"` -} - -// Config maps token addresses to their limits. -type Config struct { - Limits map[string]LimitConfig `yaml:"limits"` -} - -// limit defines the internal parsed withdrawal constraints. -type limit struct { - Hourly *big.Int - Daily *big.Int -} - -// Checker manages withdrawal limits using a database store. -type Checker struct { - limits map[common.Address]limit - store nw.WithdrawalStore - nowFunc func() time.Time -} - -// NewChecker creates a new limit checker with the provided configuration and store. -func NewChecker(cfg Config, store nw.WithdrawalStore) (*Checker, error) { - limits := make(map[common.Address]limit) - for addrStr, conf := range cfg.Limits { - if !common.IsHexAddress(addrStr) { - return nil, fmt.Errorf("invalid address in config: %s", addrStr) - } - addr := common.HexToAddress(addrStr) - - l := limit{} - if conf.Hourly != "" { - val, ok := new(big.Int).SetString(conf.Hourly, 10) - if !ok { - return nil, fmt.Errorf("invalid hourly limit for %s: %s", addrStr, conf.Hourly) - } - l.Hourly = val - } - if conf.Daily != "" { - val, ok := new(big.Int).SetString(conf.Daily, 10) - if !ok { - return nil, fmt.Errorf("invalid daily limit for %s: %s", addrStr, conf.Daily) - } - l.Daily = val - } - limits[addr] = l - } - - return &Checker{ - limits: limits, - store: store, - nowFunc: time.Now, - }, nil -} - -// Check verifies if a withdrawal amount is within limits for the given token. -// It queries the store for total withdrawn amounts in the current hour and day. -func (c *Checker) Check(token common.Address, amount *big.Int) error { - l, ok := c.limits[token] - if !ok { - return fmt.Errorf("%w: %s", ErrNoLimitsConfigured, token.Hex()) - } - - now := c.nowFunc() - - if l.Hourly != nil { - startOfHour := now.Truncate(time.Hour) - total, err := c.store.GetTotalWithdrawn(token, startOfHour) - if err != nil { - return fmt.Errorf("failed to get hourly withdrawn amount: %w", err) - } - - newTotal := new(big.Int).Add(total, amount) - if newTotal.Cmp(l.Hourly) > 0 { - return fmt.Errorf("%w for %s: %s > %s", ErrHourlyLimitExceeded, token.Hex(), newTotal, l.Hourly) - } - } - - if l.Daily != nil { - startOfDay := now.Truncate(24 * time.Hour) - total, err := c.store.GetTotalWithdrawn(token, startOfDay) - if err != nil { - return fmt.Errorf("failed to get daily withdrawn amount: %w", err) - } - - newTotal := new(big.Int).Add(total, amount) - if newTotal.Cmp(l.Daily) > 0 { - return fmt.Errorf("%w for %s: %s > %s", ErrDailyLimitExceeded, token.Hex(), newTotal, l.Daily) - } - } - - return nil -} - -// Record persists the withdrawal event to the store. -func (c *Checker) Record(w *nw.Withdrawal) error { - return c.store.Save(w) -} diff --git a/core/checker_test.go b/core/checker_test.go deleted file mode 100644 index e0a01fa..0000000 --- a/core/checker_test.go +++ /dev/null @@ -1,314 +0,0 @@ -package core - -import ( - "errors" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - nw "github.com/layer-3/nitewatch" -) - -// mockStore is an in-memory WithdrawalStore for testing. -type mockStore struct { - withdrawals []*nw.Withdrawal - err error // if set, GetTotalWithdrawn returns this error -} - -func (m *mockStore) Save(w *nw.Withdrawal) error { - m.withdrawals = append(m.withdrawals, w) - return nil -} - -func (m *mockStore) GetTotalWithdrawn(token common.Address, since time.Time) (*big.Int, error) { - if m.err != nil { - return nil, m.err - } - total := new(big.Int) - for _, w := range m.withdrawals { - if w.Token == token && !w.Timestamp.Before(since) { - total.Add(total, w.Amount) - } - } - return total, nil -} - -var ( - tokenA = common.HexToAddress("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") - tokenB = common.HexToAddress("0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") - user = common.HexToAddress("0x1111111111111111111111111111111111111111") -) - -func TestNewChecker_ValidConfig(t *testing.T) { - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, &mockStore{}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if c == nil { - t.Fatal("expected non-nil checker") - } -} - -func TestNewChecker_InvalidAddress(t *testing.T) { - cfg := Config{ - Limits: map[string]LimitConfig{ - "not-an-address": {Hourly: "1000"}, - }, - } - _, err := NewChecker(cfg, &mockStore{}) - if err == nil { - t.Fatal("expected error for invalid address") - } -} - -func TestNewChecker_InvalidHourlyLimit(t *testing.T) { - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "not-a-number"}, - }, - } - _, err := NewChecker(cfg, &mockStore{}) - if err == nil { - t.Fatal("expected error for invalid hourly limit") - } -} - -func TestNewChecker_InvalidDailyLimit(t *testing.T) { - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Daily: "xyz"}, - }, - } - _, err := NewChecker(cfg, &mockStore{}) - if err == nil { - t.Fatal("expected error for invalid daily limit") - } -} - -func TestCheck_NoLimitsConfigured(t *testing.T) { - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000"}, - }, - } - c, err := NewChecker(cfg, &mockStore{}) - if err != nil { - t.Fatal(err) - } - - err = c.Check(tokenB, big.NewInt(100)) - if !errors.Is(err, ErrNoLimitsConfigured) { - t.Fatalf("expected ErrNoLimitsConfigured, got: %v", err) - } -} - -func TestCheck_UnderHourlyLimit(t *testing.T) { - store := &mockStore{} - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { - return time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) - } - - if err := c.Check(tokenA, big.NewInt(500)); err != nil { - t.Fatalf("expected no error, got: %v", err) - } -} - -func TestCheck_ExactHourlyLimit(t *testing.T) { - store := &mockStore{} - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { - return time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) - } - - // Exact limit should pass (not exceed) - if err := c.Check(tokenA, big.NewInt(1000)); err != nil { - t.Fatalf("expected no error at exact limit, got: %v", err) - } -} - -func TestCheck_ExceedHourlyLimit(t *testing.T) { - now := time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) - store := &mockStore{ - withdrawals: []*nw.Withdrawal{ - {Token: tokenA, Amount: big.NewInt(800), Timestamp: now.Add(-10 * time.Minute)}, - }, - } - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { return now } - - err = c.Check(tokenA, big.NewInt(300)) - if !errors.Is(err, ErrHourlyLimitExceeded) { - t.Fatalf("expected ErrHourlyLimitExceeded, got: %v", err) - } -} - -func TestCheck_ExceedDailyLimit(t *testing.T) { - now := time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) - store := &mockStore{ - withdrawals: []*nw.Withdrawal{ - // Old withdrawal within the day but outside the hour - {Token: tokenA, Amount: big.NewInt(4500), Timestamp: now.Add(-3 * time.Hour)}, - }, - } - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { return now } - - err = c.Check(tokenA, big.NewInt(600)) - if !errors.Is(err, ErrDailyLimitExceeded) { - t.Fatalf("expected ErrDailyLimitExceeded, got: %v", err) - } -} - -func TestCheck_PreviousHourNotCounted(t *testing.T) { - now := time.Date(2025, 1, 1, 13, 5, 0, 0, time.UTC) - store := &mockStore{ - withdrawals: []*nw.Withdrawal{ - // Withdrawal from the previous hour - {Token: tokenA, Amount: big.NewInt(900), Timestamp: time.Date(2025, 1, 1, 12, 50, 0, 0, time.UTC)}, - }, - } - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { return now } - - // Previous hour withdrawal shouldn't count towards hourly limit - if err := c.Check(tokenA, big.NewInt(900)); err != nil { - t.Fatalf("expected no error (previous hour), got: %v", err) - } -} - -func TestCheck_HourlyOnlyConfig(t *testing.T) { - store := &mockStore{} - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { - return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) - } - - // Without daily limit, large amount within hourly should pass - if err := c.Check(tokenA, big.NewInt(999)); err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestCheck_DailyOnlyConfig(t *testing.T) { - store := &mockStore{} - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { - return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) - } - - // Without hourly limit, this should pass - if err := c.Check(tokenA, big.NewInt(4000)); err != nil { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestCheck_StoreError(t *testing.T) { - store := &mockStore{err: errors.New("db connection lost")} - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000", Daily: "5000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - c.nowFunc = func() time.Time { - return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) - } - - err = c.Check(tokenA, big.NewInt(100)) - if err == nil { - t.Fatal("expected error from store") - } -} - -func TestRecord(t *testing.T) { - store := &mockStore{} - cfg := Config{ - Limits: map[string]LimitConfig{ - tokenA.Hex(): {Hourly: "1000"}, - }, - } - c, err := NewChecker(cfg, store) - if err != nil { - t.Fatal(err) - } - - w := &nw.Withdrawal{ - WithdrawalID: [32]byte{1}, - User: user, - Token: tokenA, - Amount: big.NewInt(500), - Timestamp: time.Now(), - } - if err := c.Record(w); err != nil { - t.Fatalf("unexpected error: %v", err) - } - if len(store.withdrawals) != 1 { - t.Fatalf("expected 1 withdrawal in store, got %d", len(store.withdrawals)) - } -} diff --git a/custody/anviltest/anviltest.go b/custody/anviltest/anviltest.go new file mode 100644 index 0000000..a94c445 --- /dev/null +++ b/custody/anviltest/anviltest.go @@ -0,0 +1,146 @@ +package anviltest + +import ( + "context" + "fmt" + "sync" + "testing" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/stretchr/testify/require" + "github.com/testcontainers/testcontainers-go" + "github.com/testcontainers/testcontainers-go/wait" +) + +var ( + sharedContainer testcontainers.Container + sharedURLs URLs + sharedOnce sync.Once + sharedErr error +) + +// Anvil deterministic private keys (accounts pre-funded with 10000 ETH each). +const ( + Account0Key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" + Account1Key = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + Account2Key = "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" +) + +// URLs holds both HTTP and WebSocket URLs for an Anvil instance. +// HTTP is used for one-shot RPC calls (deploy, transact), WS for event subscriptions. +type URLs struct { + HTTP string + WS string +} + +// Setup starts an Anvil testcontainer and returns HTTP and WebSocket URLs. +// The container is automatically terminated when the test finishes. +func Setup(t *testing.T) URLs { + t.Helper() + + ctx := t.Context() + + anvilContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "ghcr.io/foundry-rs/foundry:latest", + ExposedPorts: []string{"8545/tcp"}, + // Image ENTRYPOINT is ["/bin/sh", "-c"], so the command must be a single string. + Cmd: []string{"anvil --host 0.0.0.0"}, + WaitingFor: wait.ForAll( + wait.ForLog("Listening on"), + wait.ForListeningPort("8545/tcp"), + ), + }, + Started: true, + }) + require.NoError(t, err) + + t.Cleanup(func() { + if err := anvilContainer.Terminate(context.Background()); err != nil { + t.Log("Anvil container terminated with error:", err) + } + }) + + host, err := anvilContainer.Host(ctx) + require.NoError(t, err) + + port, err := anvilContainer.MappedPort(ctx, "8545") + require.NoError(t, err) + + return URLs{ + HTTP: fmt.Sprintf("http://%s:%s", host, port.Port()), + WS: fmt.Sprintf("ws://%s:%s", host, port.Port()), + } +} + +// SetupShared starts a shared Anvil container (once) and returns its URLs. +// Call TerminateShared in TestMain to clean up after all tests. +func SetupShared(ctx context.Context) (URLs, error) { + sharedOnce.Do(func() { + var container testcontainers.Container + container, sharedErr = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + ContainerRequest: testcontainers.ContainerRequest{ + Image: "ghcr.io/foundry-rs/foundry:latest", + ExposedPorts: []string{"8545/tcp"}, + Cmd: []string{"anvil --host 0.0.0.0"}, + WaitingFor: wait.ForAll( + wait.ForLog("Listening on"), + wait.ForListeningPort("8545/tcp"), + ), + }, + Started: true, + }) + if sharedErr != nil { + return + } + sharedContainer = container + + host, err := container.Host(ctx) + if err != nil { + sharedErr = err + return + } + port, err := container.MappedPort(ctx, "8545") + if err != nil { + sharedErr = err + return + } + sharedURLs = URLs{ + HTTP: fmt.Sprintf("http://%s:%s", host, port.Port()), + WS: fmt.Sprintf("ws://%s:%s", host, port.Port()), + } + }) + return sharedURLs, sharedErr +} + +// TerminateShared stops the shared Anvil container. Call from TestMain. +func TerminateShared(ctx context.Context) error { + if sharedContainer != nil { + return sharedContainer.Terminate(ctx) + } + return nil +} + +// DialHTTP connects to an Anvil node via HTTP with retry for container readiness. +// Retries up to 10 times with 500ms backoff, verifying with a ChainID() call. +func DialHTTP(t *testing.T, httpURL string) *ethclient.Client { + t.Helper() + + var client *ethclient.Client + var err error + for i := 0; i < 10; i++ { + client, err = ethclient.Dial(httpURL) + if err == nil { + _, chainErr := client.ChainID(context.Background()) + if chainErr == nil { + return client + } + client.Close() + err = chainErr + } + time.Sleep(500 * time.Millisecond) + } + require.NoError(t, err, "failed to connect to Anvil after retries") + return nil +} diff --git a/custody/ethlistener.go b/custody/ethlistener.go new file mode 100644 index 0000000..f2e5bfa --- /dev/null +++ b/custody/ethlistener.go @@ -0,0 +1,263 @@ +// Copied from github.com/layer-3/pathfinder/pkg/ethlistener (commit hash 686dc94b80985eba798fdec499b9a802dbf80471). +// Adapted: replaced slog with ipfs/go-log, removed FetchHistoricalLogs, +// which depends on pathfinder's ethclient package. +package custody + +import ( + "context" + "errors" + "fmt" + "math/big" + "regexp" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + logging "github.com/ipfs/go-log/v2" + "github.com/layer-3/clearsync/pkg/debounce" +) + +var ethLogger = logging.Logger("ethlistener") + +const ( + maxBackOffCount = 5 +) + +type logHandler func(log types.Log) + +func listenEvents( + ctx context.Context, + client bind.ContractBackend, + subID string, + contractAddress common.Address, + networkID uint32, + lastBlock uint64, + lastIndex uint32, + topics [][]common.Hash, + handler logHandler, +) { + var backOffCount atomic.Uint64 + var historicalCh, currentCh chan types.Log + var eventSubscription event.Subscription + + ethLogger.Debugw("starting listening events", "subID", subID, "contractAddress", contractAddress.String()) + for { + if err := ctx.Err(); err != nil { + ethLogger.Infow("context cancelled, stopping listener", "subID", subID) + if eventSubscription != nil { + eventSubscription.Unsubscribe() + } + return + } + + if eventSubscription == nil { + if !waitForBackOffTimeout(ctx, int(backOffCount.Load()), "event subscription") { + return + } + + historicalCh = make(chan types.Log, 1) + currentCh = make(chan types.Log, 100) + + if lastBlock == 0 { + ethLogger.Infow("skipping historical logs fetching", "subID", subID, "contractAddress", contractAddress.String()) + } else { + var header *types.Header + var err error + headerCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) + err = debounce.Debounce(headerCtx, ethLogger, func(ctx context.Context) error { + header, err = client.HeaderByNumber(ctx, nil) + return err + }) + cancel() + if err != nil { + if ctx.Err() != nil { + return + } + ethLogger.Errorw("failed to get latest block", "error", err, "subID", subID, "contractAddress", contractAddress.String()) + backOffCount.Add(1) + continue + } + + go reconcileBlockRange( + ctx, + client, + subID, + contractAddress, + networkID, + header.Number.Uint64(), + lastBlock, + lastIndex, + topics, + historicalCh, + ) + } + + watchFQ := ethereum.FilterQuery{ + Addresses: []common.Address{contractAddress}, + } + eventSub, err := client.SubscribeFilterLogs(ctx, watchFQ, currentCh) + if err != nil { + if ctx.Err() != nil { + return + } + ethLogger.Errorw("failed to subscribe on events", "error", err, "subID", subID, "contractAddress", contractAddress.String()) + backOffCount.Add(1) + continue + } + + eventSubscription = eventSub + ethLogger.Infow("watching events", "subID", subID, "contractAddress", contractAddress.String()) + backOffCount.Store(0) + } + + select { + case <-ctx.Done(): + ethLogger.Infow("context cancelled, stopping listener", "subID", subID) + eventSubscription.Unsubscribe() + return + case eventLog := <-historicalCh: + ethLogger.Debugw("received historical event", "subID", subID, "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) + handler(eventLog) + case eventLog := <-currentCh: + lastBlock = eventLog.BlockNumber + ethLogger.Debugw("received new event", "subID", subID, "blockNumber", lastBlock, "logIndex", eventLog.Index) + handler(eventLog) + case err := <-eventSubscription.Err(): + if err != nil { + ethLogger.Errorw("event subscription error", "error", err, "subID", subID, "contractAddress", contractAddress.String()) + eventSubscription.Unsubscribe() + } else { + ethLogger.Debugw("subscription closed, resubscribing", "subID", subID, "contractAddress", contractAddress.String()) + } + + eventSubscription = nil + } + } +} + +func reconcileBlockRange( + ctx context.Context, + client bind.ContractBackend, + subID string, + contractAddress common.Address, + networkID uint32, + currentBlock uint64, + lastBlock uint64, + lastIndex uint32, + topics [][]common.Hash, + historicalCh chan types.Log, +) { + var backOffCount atomic.Uint64 + const blockStep = 10000 + startBlock := lastBlock + endBlock := startBlock + blockStep + + for currentBlock > startBlock { + if ctx.Err() != nil { + return + } + if !waitForBackOffTimeout(ctx, int(backOffCount.Load()), "reconcile block range") { + return + } + + if endBlock > currentBlock { + endBlock = currentBlock + } + + fetchFQ := ethereum.FilterQuery{ + Addresses: []common.Address{contractAddress}, + FromBlock: new(big.Int).SetUint64(startBlock), + ToBlock: new(big.Int).SetUint64(endBlock), + Topics: topics, + } + + var logs []types.Log + var err error + logsCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) + err = debounce.Debounce(logsCtx, ethLogger, func(ctx context.Context) error { + logs, err = client.FilterLogs(ctx, fetchFQ) + return err + }) + cancel() + if err != nil { + if strings.Contains(err.Error(), "Exceeded max range limit for eth_getLogs:") { + newEndBlock := endBlock - (endBlock-startBlock)/2 + ethLogger.Infow("eth_getLogs exceeded max range limit, reducing block range", "subID", subID, "startBlock", startBlock, "oldEndBlock", endBlock, "newEndBlock", newEndBlock) + endBlock = newEndBlock + continue + } + + newStartBlock, newEndBlock, extractErr := extractAdvisedBlockRange(err.Error()) + if extractErr != nil { + ethLogger.Errorw("failed to filter logs", "error", err, "extractErr", extractErr, "subID", subID, "startBlock", startBlock, "endBlock", endBlock) + backOffCount.Add(1) + continue + } + startBlock, endBlock = newStartBlock, newEndBlock + ethLogger.Infow("retrying with advised block range", "subID", subID, "startBlock", startBlock, "endBlock", endBlock) + continue + } + ethLogger.Infow("fetched historical logs", "subID", subID, "count", len(logs), "startBlock", startBlock, "endBlock", endBlock) + + for _, ethLog := range logs { + if ethLog.BlockNumber == lastBlock && ethLog.Index <= uint(lastIndex) { + ethLogger.Infow("skipping previously known event", "subID", subID, "blockNumber", ethLog.BlockNumber, "logIndex", ethLog.Index) + continue + } + + historicalCh <- ethLog + } + + startBlock = endBlock + 1 + endBlock += blockStep + } +} + +func extractAdvisedBlockRange(msg string) (startBlock, endBlock uint64, err error) { + if !strings.Contains(msg, "query returned more than 10000 results") { + err = errors.New("error message doesn't contain advised block range") + return + } + + re := regexp.MustCompile(`\[0x([0-9a-fA-F]+), 0x([0-9a-fA-F]+)\]`) + match := re.FindStringSubmatch(msg) + if len(match) != 3 { + err = errors.New("failed to extract block range from error message") + return + } + + startBlock, err = strconv.ParseUint(match[1], 16, 64) + if err != nil { + err = fmt.Errorf("failed to parse block range from error message: %w", err) + return + } + endBlock, err = strconv.ParseUint(match[2], 16, 64) + if err != nil { + err = fmt.Errorf("failed to parse block range from error message: %w", err) + return + } + return +} + +func waitForBackOffTimeout(ctx context.Context, backOffCount int, originator string) bool { + if backOffCount > maxBackOffCount { + ethLogger.Errorw("back off limit reached, exiting", "originator", originator, "backOffCount", backOffCount) + return true + } + + if backOffCount > 0 { + ethLogger.Infow("backing off", "originator", originator, "backOffCount", backOffCount) + select { + case <-time.After(time.Duration(2^backOffCount-1) * time.Second): + case <-ctx.Done(): + return false + } + } + return true +} diff --git a/custody/ideposit.go b/custody/ideposit.go new file mode 100644 index 0000000..f0988aa --- /dev/null +++ b/custody/ideposit.go @@ -0,0 +1,356 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package custody + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// IDepositMetaData contains all meta data concerning the IDeposit contract. +var IDepositMetaData = &bind.MetaData{ + ABI: "[{\"type\":\"function\",\"name\":\"deposit\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"event\",\"name\":\"Deposited\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"MsgValueMismatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NonZeroMsgValueForERC20\",\"inputs\":[]}]", +} + +// IDepositABI is the input ABI used to generate the binding from. +// Deprecated: Use IDepositMetaData.ABI instead. +var IDepositABI = IDepositMetaData.ABI + +// IDeposit is an auto generated Go binding around an Ethereum contract. +type IDeposit struct { + IDepositCaller // Read-only binding to the contract + IDepositTransactor // Write-only binding to the contract + IDepositFilterer // Log filterer for contract events +} + +// IDepositCaller is an auto generated read-only Go binding around an Ethereum contract. +type IDepositCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IDepositTransactor is an auto generated write-only Go binding around an Ethereum contract. +type IDepositTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IDepositFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type IDepositFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// IDepositSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type IDepositSession struct { + Contract *IDeposit // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// IDepositCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type IDepositCallerSession struct { + Contract *IDepositCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// IDepositTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type IDepositTransactorSession struct { + Contract *IDepositTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// IDepositRaw is an auto generated low-level Go binding around an Ethereum contract. +type IDepositRaw struct { + Contract *IDeposit // Generic contract binding to access the raw methods on +} + +// IDepositCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type IDepositCallerRaw struct { + Contract *IDepositCaller // Generic read-only contract binding to access the raw methods on +} + +// IDepositTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type IDepositTransactorRaw struct { + Contract *IDepositTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewIDeposit creates a new instance of IDeposit, bound to a specific deployed contract. +func NewIDeposit(address common.Address, backend bind.ContractBackend) (*IDeposit, error) { + contract, err := bindIDeposit(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &IDeposit{IDepositCaller: IDepositCaller{contract: contract}, IDepositTransactor: IDepositTransactor{contract: contract}, IDepositFilterer: IDepositFilterer{contract: contract}}, nil +} + +// NewIDepositCaller creates a new read-only instance of IDeposit, bound to a specific deployed contract. +func NewIDepositCaller(address common.Address, caller bind.ContractCaller) (*IDepositCaller, error) { + contract, err := bindIDeposit(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &IDepositCaller{contract: contract}, nil +} + +// NewIDepositTransactor creates a new write-only instance of IDeposit, bound to a specific deployed contract. +func NewIDepositTransactor(address common.Address, transactor bind.ContractTransactor) (*IDepositTransactor, error) { + contract, err := bindIDeposit(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &IDepositTransactor{contract: contract}, nil +} + +// NewIDepositFilterer creates a new log filterer instance of IDeposit, bound to a specific deployed contract. +func NewIDepositFilterer(address common.Address, filterer bind.ContractFilterer) (*IDepositFilterer, error) { + contract, err := bindIDeposit(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &IDepositFilterer{contract: contract}, nil +} + +// bindIDeposit binds a generic wrapper to an already deployed contract. +func bindIDeposit(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := IDepositMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_IDeposit *IDepositRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _IDeposit.Contract.IDepositCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_IDeposit *IDepositRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _IDeposit.Contract.IDepositTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_IDeposit *IDepositRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _IDeposit.Contract.IDepositTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_IDeposit *IDepositCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _IDeposit.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_IDeposit *IDepositTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _IDeposit.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_IDeposit *IDepositTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _IDeposit.Contract.contract.Transact(opts, method, params...) +} + +// Deposit is a paid mutator transaction binding the contract method 0x47e7ef24. +// +// Solidity: function deposit(address token, uint256 amount) payable returns() +func (_IDeposit *IDepositTransactor) Deposit(opts *bind.TransactOpts, token common.Address, amount *big.Int) (*types.Transaction, error) { + return _IDeposit.contract.Transact(opts, "deposit", token, amount) +} + +// Deposit is a paid mutator transaction binding the contract method 0x47e7ef24. +// +// Solidity: function deposit(address token, uint256 amount) payable returns() +func (_IDeposit *IDepositSession) Deposit(token common.Address, amount *big.Int) (*types.Transaction, error) { + return _IDeposit.Contract.Deposit(&_IDeposit.TransactOpts, token, amount) +} + +// Deposit is a paid mutator transaction binding the contract method 0x47e7ef24. +// +// Solidity: function deposit(address token, uint256 amount) payable returns() +func (_IDeposit *IDepositTransactorSession) Deposit(token common.Address, amount *big.Int) (*types.Transaction, error) { + return _IDeposit.Contract.Deposit(&_IDeposit.TransactOpts, token, amount) +} + +// IDepositDepositedIterator is returned from FilterDeposited and is used to iterate over the raw logs and unpacked data for Deposited events raised by the IDeposit contract. +type IDepositDepositedIterator struct { + Event *IDepositDeposited // 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 *IDepositDepositedIterator) 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(IDepositDeposited) + 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(IDepositDeposited) + 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 *IDepositDepositedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *IDepositDepositedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// IDepositDeposited represents a Deposited event raised by the IDeposit contract. +type IDepositDeposited struct { + User common.Address + Token common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterDeposited is a free log retrieval operation binding the contract event 0x8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7. +// +// Solidity: event Deposited(address indexed user, address indexed token, uint256 amount) +func (_IDeposit *IDepositFilterer) FilterDeposited(opts *bind.FilterOpts, user []common.Address, token []common.Address) (*IDepositDepositedIterator, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + var tokenRule []interface{} + for _, tokenItem := range token { + tokenRule = append(tokenRule, tokenItem) + } + + logs, sub, err := _IDeposit.contract.FilterLogs(opts, "Deposited", userRule, tokenRule) + if err != nil { + return nil, err + } + return &IDepositDepositedIterator{contract: _IDeposit.contract, event: "Deposited", logs: logs, sub: sub}, nil +} + +// WatchDeposited is a free log subscription operation binding the contract event 0x8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7. +// +// Solidity: event Deposited(address indexed user, address indexed token, uint256 amount) +func (_IDeposit *IDepositFilterer) WatchDeposited(opts *bind.WatchOpts, sink chan<- *IDepositDeposited, user []common.Address, token []common.Address) (event.Subscription, error) { + + var userRule []interface{} + for _, userItem := range user { + userRule = append(userRule, userItem) + } + var tokenRule []interface{} + for _, tokenItem := range token { + tokenRule = append(tokenRule, tokenItem) + } + + logs, sub, err := _IDeposit.contract.WatchLogs(opts, "Deposited", userRule, tokenRule) + 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(IDepositDeposited) + if err := _IDeposit.contract.UnpackLog(event, "Deposited", 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 +} + +// ParseDeposited is a log parse operation binding the contract event 0x8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a7. +// +// Solidity: event Deposited(address indexed user, address indexed token, uint256 amount) +func (_IDeposit *IDepositFilterer) ParseDeposited(log types.Log) (*IDepositDeposited, error) { + event := new(IDepositDeposited) + if err := _IDeposit.contract.UnpackLog(event, "Deposited", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/chain/iwithdraw.go b/custody/iwithdraw.go similarity index 99% rename from chain/iwithdraw.go rename to custody/iwithdraw.go index ea3f63f..a055393 100644 --- a/chain/iwithdraw.go +++ b/custody/iwithdraw.go @@ -1,7 +1,7 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package chain +package custody import ( "errors" diff --git a/custody/listener.go b/custody/listener.go new file mode 100644 index 0000000..3629511 --- /dev/null +++ b/custody/listener.go @@ -0,0 +1,134 @@ +package custody + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Listener handles monitoring the blockchain for events from the custody contract. +type Listener struct { + client bind.ContractBackend + contractAddr common.Address + withdrawFilterer *IWithdrawFilterer + depositFilterer *IDepositFilterer +} + +// NewListener creates a new Listener instance. +// client: an Ethereum client supporting log subscriptions (e.g. *ethclient.Client via WebSocket) +// contractAddr: address of the custody contract +// withdraw: bound IWithdraw contract instance +// deposit: bound IDeposit contract instance (can be nil if deposit events are not needed) +func NewListener(client bind.ContractBackend, contractAddr common.Address, withdraw *IWithdraw, deposit *IDeposit) *Listener { + l := &Listener{ + client: client, + contractAddr: contractAddr, + } + if withdraw != nil { + l.withdrawFilterer = &withdraw.IWithdrawFilterer + } + if deposit != nil { + l.depositFilterer = &deposit.IDepositFilterer + } + return l +} + +// Compile-time check that Listener implements EventListener. +var _ EventListener = (*Listener)(nil) + +// WatchWithdrawStarted subscribes to WithdrawStarted events and sends them to the sink channel. +// This function blocks forever; run it in a goroutine. The sink channel is closed when it returns. +func (l *Listener) WatchWithdrawStarted(ctx context.Context, sink chan<- *WithdrawStartedEvent, fromBlock uint64, fromLogIndex uint32) { + defer close(sink) + + parsedABI, err := IWithdrawMetaData.GetAbi() + if err != nil { + return + } + topic := parsedABI.Events["WithdrawStarted"].ID + + listenEvents(ctx, l.client, "withdraw-started", l.contractAddr, 0, fromBlock, fromLogIndex, + [][]common.Hash{{topic}}, + func(log types.Log) { + ev, err := l.withdrawFilterer.ParseWithdrawStarted(log) + if err != nil { + return + } + sink <- &WithdrawStartedEvent{ + WithdrawalID: ev.WithdrawalId, + User: ev.User, + Token: ev.Token, + Amount: ev.Amount, + Nonce: ev.Nonce, + BlockNumber: ev.Raw.BlockNumber, + TxHash: ev.Raw.TxHash, + LogIndex: ev.Raw.Index, + } + }, + ) +} + +// WatchWithdrawFinalized subscribes to WithdrawFinalized events and sends them to the sink channel. +// This function blocks forever; run it in a goroutine. The sink channel is closed when it returns. +func (l *Listener) WatchWithdrawFinalized(ctx context.Context, sink chan<- *WithdrawFinalizedEvent, fromBlock uint64, fromLogIndex uint32) { + defer close(sink) + + parsedABI, err := IWithdrawMetaData.GetAbi() + if err != nil { + return + } + topic := parsedABI.Events["WithdrawFinalized"].ID + + listenEvents(ctx, l.client, "withdraw-finalized", l.contractAddr, 0, fromBlock, fromLogIndex, + [][]common.Hash{{topic}}, + func(log types.Log) { + ev, err := l.withdrawFilterer.ParseWithdrawFinalized(log) + if err != nil { + return + } + sink <- &WithdrawFinalizedEvent{ + WithdrawalID: ev.WithdrawalId, + Success: ev.Success, + BlockNumber: ev.Raw.BlockNumber, + TxHash: ev.Raw.TxHash, + LogIndex: ev.Raw.Index, + } + }, + ) +} + +// WatchDeposited subscribes to Deposited events and sends them to the sink channel. +// This function blocks forever; run it in a goroutine. The sink channel is closed when it returns. +func (l *Listener) WatchDeposited(ctx context.Context, sink chan<- *DepositedEvent, fromBlock uint64, fromLogIndex uint32) { + defer close(sink) + + if l.depositFilterer == nil { + return + } + + parsedABI, err := IDepositMetaData.GetAbi() + if err != nil { + return + } + topic := parsedABI.Events["Deposited"].ID + + listenEvents(ctx, l.client, "deposited", l.contractAddr, 0, fromBlock, fromLogIndex, + [][]common.Hash{{topic}}, + func(log types.Log) { + ev, err := l.depositFilterer.ParseDeposited(log) + if err != nil { + return + } + sink <- &DepositedEvent{ + User: ev.User, + Token: ev.Token, + Amount: ev.Amount, + BlockNumber: ev.Raw.BlockNumber, + TxHash: ev.Raw.TxHash, + LogIndex: ev.Raw.Index, + } + }, + ) +} diff --git a/custody/mock_erc20.go b/custody/mock_erc20.go new file mode 100644 index 0000000..7ee9ba8 --- /dev/null +++ b/custody/mock_erc20.go @@ -0,0 +1,781 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package custody + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// MockERC20MetaData contains all meta data concerning the MockERC20 contract. +var MockERC20MetaData = &bind.MetaData{ + ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"mint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"ERC20InsufficientAllowance\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"allowance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"needed\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ERC20InsufficientBalance\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"balance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"needed\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidApprover\",\"inputs\":[{\"name\":\"approver\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidReceiver\",\"inputs\":[{\"name\":\"receiver\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidSender\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidSpender\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}]}]", + Bin: "0x608060405234801561000f575f5ffd5b506040518060400160405280600481526020017f4d6f636b000000000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d434b0000000000000000000000000000000000000000000000000000000000815250816003908161008b91906102e0565b50806004908161009b91906102e0565b5050506103af565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061011e57607f821691505b602082108103610131576101306100da565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026101937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610158565b61019d8683610158565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6101e16101dc6101d7846101b5565b6101be565b6101b5565b9050919050565b5f819050919050565b6101fa836101c7565b61020e610206826101e8565b848454610164565b825550505050565b5f5f905090565b610225610216565b6102308184846101f1565b505050565b5b81811015610253576102485f8261021d565b600181019050610236565b5050565b601f8211156102985761026981610137565b61027284610149565b81016020851015610281578190505b61029561028d85610149565b830182610235565b50505b505050565b5f82821c905092915050565b5f6102b85f198460080261029d565b1980831691505092915050565b5f6102d083836102a9565b9150826002028217905092915050565b6102e9826100a3565b67ffffffffffffffff811115610302576103016100ad565b5b61030c8254610107565b610317828285610257565b5f60209050601f831160018114610348575f8415610336578287015190505b61034085826102c5565b8655506103a7565b601f19841661035686610137565b5f5b8281101561037d57848901518255600182019150602085019450602081019050610358565b8683101561039a5784890151610396601f8916826102a9565b8355505b6001600288020188555050505b505050505050565b610e96806103bc5f395ff3fe608060405234801561000f575f5ffd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f5ffd5b6100a8610224565b6040516100b59190610b0f565b60405180910390f35b6100d860048036038101906100d39190610bc0565b6102b4565b6040516100e59190610c18565b60405180910390f35b6100f66102d6565b6040516101039190610c40565b60405180910390f35b61012660048036038101906101219190610c59565b6102df565b6040516101339190610c18565b60405180910390f35b61014461030d565b6040516101519190610cc4565b60405180910390f35b610174600480360381019061016f9190610bc0565b610315565b005b610190600480360381019061018b9190610cdd565b610323565b60405161019d9190610c40565b60405180910390f35b6101ae610368565b6040516101bb9190610b0f565b60405180910390f35b6101de60048036038101906101d99190610bc0565b6103f8565b6040516101eb9190610c18565b60405180910390f35b61020e60048036038101906102099190610d08565b61041a565b60405161021b9190610c40565b60405180910390f35b60606003805461023390610d73565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610d73565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f5f6102be61049c565b90506102cb8185856104a3565b600191505092915050565b5f600254905090565b5f5f6102e961049c565b90506102f68582856104b5565b610301858585610548565b60019150509392505050565b5f6012905090565b61031f8282610638565b5050565b5f5f5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606004805461037790610d73565b80601f01602080910402602001604051908101604052809291908181526020018280546103a390610d73565b80156103ee5780601f106103c5576101008083540402835291602001916103ee565b820191905f5260205f20905b8154815290600101906020018083116103d157829003601f168201915b5050505050905090565b5f5f61040261049c565b905061040f818585610548565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b6104b083838360016106b7565b505050565b5f6104c0848461041a565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156105425781811015610533578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161052a93929190610db2565b60405180910390fd5b61054184848484035f6106b7565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105b8575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016105af9190610de7565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610628575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161061f9190610de7565b60405180910390fd5b610633838383610886565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106a8575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161069f9190610de7565b60405180910390fd5b6106b35f8383610886565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610727575f6040517fe602df0500000000000000000000000000000000000000000000000000000000815260040161071e9190610de7565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610797575f6040517f94280d6200000000000000000000000000000000000000000000000000000000815260040161078e9190610de7565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610880578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516108779190610c40565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108d6578060025f8282546108ca9190610e2d565b925050819055506109a4565b5f5f5f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205490508181101561095f578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161095693929190610db2565b60405180910390fd5b8181035f5f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036109eb578060025f8282540392505081905550610a35565b805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610a929190610c40565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610ae182610a9f565b610aeb8185610aa9565b9350610afb818560208601610ab9565b610b0481610ac7565b840191505092915050565b5f6020820190508181035f830152610b278184610ad7565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b5c82610b33565b9050919050565b610b6c81610b52565b8114610b76575f5ffd5b50565b5f81359050610b8781610b63565b92915050565b5f819050919050565b610b9f81610b8d565b8114610ba9575f5ffd5b50565b5f81359050610bba81610b96565b92915050565b5f5f60408385031215610bd657610bd5610b2f565b5b5f610be385828601610b79565b9250506020610bf485828601610bac565b9150509250929050565b5f8115159050919050565b610c1281610bfe565b82525050565b5f602082019050610c2b5f830184610c09565b92915050565b610c3a81610b8d565b82525050565b5f602082019050610c535f830184610c31565b92915050565b5f5f5f60608486031215610c7057610c6f610b2f565b5b5f610c7d86828701610b79565b9350506020610c8e86828701610b79565b9250506040610c9f86828701610bac565b9150509250925092565b5f60ff82169050919050565b610cbe81610ca9565b82525050565b5f602082019050610cd75f830184610cb5565b92915050565b5f60208284031215610cf257610cf1610b2f565b5b5f610cff84828501610b79565b91505092915050565b5f5f60408385031215610d1e57610d1d610b2f565b5b5f610d2b85828601610b79565b9250506020610d3c85828601610b79565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610d8a57607f821691505b602082108103610d9d57610d9c610d46565b5b50919050565b610dac81610b52565b82525050565b5f606082019050610dc55f830186610da3565b610dd26020830185610c31565b610ddf6040830184610c31565b949350505050565b5f602082019050610dfa5f830184610da3565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e3782610b8d565b9150610e4283610b8d565b9250828201905080821115610e5a57610e59610e00565b5b9291505056fea264697066735822122094811535dfcfe0091b41027b6057239a0ab967fa4a0862a19f78329809ea0cbc64736f6c634300081e0033", +} + +// MockERC20ABI is the input ABI used to generate the binding from. +// Deprecated: Use MockERC20MetaData.ABI instead. +var MockERC20ABI = MockERC20MetaData.ABI + +// MockERC20Bin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use MockERC20MetaData.Bin instead. +var MockERC20Bin = MockERC20MetaData.Bin + +// DeployMockERC20 deploys a new Ethereum contract, binding an instance of MockERC20 to it. +func DeployMockERC20(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *MockERC20, error) { + parsed, err := MockERC20MetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(MockERC20Bin), backend) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &MockERC20{MockERC20Caller: MockERC20Caller{contract: contract}, MockERC20Transactor: MockERC20Transactor{contract: contract}, MockERC20Filterer: MockERC20Filterer{contract: contract}}, nil +} + +// MockERC20 is an auto generated Go binding around an Ethereum contract. +type MockERC20 struct { + MockERC20Caller // Read-only binding to the contract + MockERC20Transactor // Write-only binding to the contract + MockERC20Filterer // Log filterer for contract events +} + +// MockERC20Caller is an auto generated read-only Go binding around an Ethereum contract. +type MockERC20Caller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MockERC20Transactor is an auto generated write-only Go binding around an Ethereum contract. +type MockERC20Transactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MockERC20Filterer is an auto generated log filtering Go binding around an Ethereum contract events. +type MockERC20Filterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// MockERC20Session is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type MockERC20Session struct { + Contract *MockERC20 // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MockERC20CallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type MockERC20CallerSession struct { + Contract *MockERC20Caller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// MockERC20TransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type MockERC20TransactorSession struct { + Contract *MockERC20Transactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// MockERC20Raw is an auto generated low-level Go binding around an Ethereum contract. +type MockERC20Raw struct { + Contract *MockERC20 // Generic contract binding to access the raw methods on +} + +// MockERC20CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type MockERC20CallerRaw struct { + Contract *MockERC20Caller // Generic read-only contract binding to access the raw methods on +} + +// MockERC20TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type MockERC20TransactorRaw struct { + Contract *MockERC20Transactor // Generic write-only contract binding to access the raw methods on +} + +// NewMockERC20 creates a new instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20(address common.Address, backend bind.ContractBackend) (*MockERC20, error) { + contract, err := bindMockERC20(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &MockERC20{MockERC20Caller: MockERC20Caller{contract: contract}, MockERC20Transactor: MockERC20Transactor{contract: contract}, MockERC20Filterer: MockERC20Filterer{contract: contract}}, nil +} + +// NewMockERC20Caller creates a new read-only instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20Caller(address common.Address, caller bind.ContractCaller) (*MockERC20Caller, error) { + contract, err := bindMockERC20(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &MockERC20Caller{contract: contract}, nil +} + +// NewMockERC20Transactor creates a new write-only instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20Transactor(address common.Address, transactor bind.ContractTransactor) (*MockERC20Transactor, error) { + contract, err := bindMockERC20(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &MockERC20Transactor{contract: contract}, nil +} + +// NewMockERC20Filterer creates a new log filterer instance of MockERC20, bound to a specific deployed contract. +func NewMockERC20Filterer(address common.Address, filterer bind.ContractFilterer) (*MockERC20Filterer, error) { + contract, err := bindMockERC20(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &MockERC20Filterer{contract: contract}, nil +} + +// bindMockERC20 binds a generic wrapper to an already deployed contract. +func bindMockERC20(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := MockERC20MetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MockERC20 *MockERC20Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MockERC20.Contract.MockERC20Caller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MockERC20 *MockERC20Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MockERC20.Contract.MockERC20Transactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MockERC20 *MockERC20Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MockERC20.Contract.MockERC20Transactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_MockERC20 *MockERC20CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _MockERC20.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_MockERC20 *MockERC20TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _MockERC20.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_MockERC20 *MockERC20TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _MockERC20.Contract.contract.Transact(opts, method, params...) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_MockERC20 *MockERC20Caller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "allowance", owner, spender) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_MockERC20 *MockERC20Session) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _MockERC20.Contract.Allowance(&_MockERC20.CallOpts, owner, spender) +} + +// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. +// +// Solidity: function allowance(address owner, address spender) view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { + return _MockERC20.Contract.Allowance(&_MockERC20.CallOpts, owner, spender) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_MockERC20 *MockERC20Caller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "balanceOf", account) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_MockERC20 *MockERC20Session) BalanceOf(account common.Address) (*big.Int, error) { + return _MockERC20.Contract.BalanceOf(&_MockERC20.CallOpts, account) +} + +// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. +// +// Solidity: function balanceOf(address account) view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) BalanceOf(account common.Address) (*big.Int, error) { + return _MockERC20.Contract.BalanceOf(&_MockERC20.CallOpts, account) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_MockERC20 *MockERC20Caller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "decimals") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_MockERC20 *MockERC20Session) Decimals() (uint8, error) { + return _MockERC20.Contract.Decimals(&_MockERC20.CallOpts) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_MockERC20 *MockERC20CallerSession) Decimals() (uint8, error) { + return _MockERC20.Contract.Decimals(&_MockERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_MockERC20 *MockERC20Caller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "name") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_MockERC20 *MockERC20Session) Name() (string, error) { + return _MockERC20.Contract.Name(&_MockERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_MockERC20 *MockERC20CallerSession) Name() (string, error) { + return _MockERC20.Contract.Name(&_MockERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_MockERC20 *MockERC20Caller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "symbol") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_MockERC20 *MockERC20Session) Symbol() (string, error) { + return _MockERC20.Contract.Symbol(&_MockERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_MockERC20 *MockERC20CallerSession) Symbol() (string, error) { + return _MockERC20.Contract.Symbol(&_MockERC20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_MockERC20 *MockERC20Caller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _MockERC20.contract.Call(opts, &out, "totalSupply") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_MockERC20 *MockERC20Session) TotalSupply() (*big.Int, error) { + return _MockERC20.Contract.TotalSupply(&_MockERC20.CallOpts) +} + +// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. +// +// Solidity: function totalSupply() view returns(uint256) +func (_MockERC20 *MockERC20CallerSession) TotalSupply() (*big.Int, error) { + return _MockERC20.Contract.TotalSupply(&_MockERC20.CallOpts) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 value) returns(bool) +func (_MockERC20 *MockERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "approve", spender, value) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 value) returns(bool) +func (_MockERC20 *MockERC20Session) Approve(spender common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Approve(&_MockERC20.TransactOpts, spender, value) +} + +// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. +// +// Solidity: function approve(address spender, uint256 value) returns(bool) +func (_MockERC20 *MockERC20TransactorSession) Approve(spender common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Approve(&_MockERC20.TransactOpts, spender, value) +} + +// Mint is a paid mutator transaction binding the contract method 0x40c10f19. +// +// Solidity: function mint(address to, uint256 amount) returns() +func (_MockERC20 *MockERC20Transactor) Mint(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "mint", to, amount) +} + +// Mint is a paid mutator transaction binding the contract method 0x40c10f19. +// +// Solidity: function mint(address to, uint256 amount) returns() +func (_MockERC20 *MockERC20Session) Mint(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Mint(&_MockERC20.TransactOpts, to, amount) +} + +// Mint is a paid mutator transaction binding the contract method 0x40c10f19. +// +// Solidity: function mint(address to, uint256 amount) returns() +func (_MockERC20 *MockERC20TransactorSession) Mint(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Mint(&_MockERC20.TransactOpts, to, amount) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 value) returns(bool) +func (_MockERC20 *MockERC20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "transfer", to, value) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 value) returns(bool) +func (_MockERC20 *MockERC20Session) Transfer(to common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Transfer(&_MockERC20.TransactOpts, to, value) +} + +// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. +// +// Solidity: function transfer(address to, uint256 value) returns(bool) +func (_MockERC20 *MockERC20TransactorSession) Transfer(to common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.Transfer(&_MockERC20.TransactOpts, to, value) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) +func (_MockERC20 *MockERC20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.contract.Transact(opts, "transferFrom", from, to, value) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) +func (_MockERC20 *MockERC20Session) TransferFrom(from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.TransferFrom(&_MockERC20.TransactOpts, from, to, value) +} + +// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. +// +// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) +func (_MockERC20 *MockERC20TransactorSession) TransferFrom(from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { + return _MockERC20.Contract.TransferFrom(&_MockERC20.TransactOpts, from, to, value) +} + +// MockERC20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the MockERC20 contract. +type MockERC20ApprovalIterator struct { + Event *MockERC20Approval // 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 *MockERC20ApprovalIterator) 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(MockERC20Approval) + 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(MockERC20Approval) + 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 *MockERC20ApprovalIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MockERC20ApprovalIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MockERC20Approval represents a Approval event raised by the MockERC20 contract. +type MockERC20Approval struct { + Owner common.Address + Spender common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_MockERC20 *MockERC20Filterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*MockERC20ApprovalIterator, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _MockERC20.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) + if err != nil { + return nil, err + } + return &MockERC20ApprovalIterator{contract: _MockERC20.contract, event: "Approval", logs: logs, sub: sub}, nil +} + +// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_MockERC20 *MockERC20Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *MockERC20Approval, owner []common.Address, spender []common.Address) (event.Subscription, error) { + + var ownerRule []interface{} + for _, ownerItem := range owner { + ownerRule = append(ownerRule, ownerItem) + } + var spenderRule []interface{} + for _, spenderItem := range spender { + spenderRule = append(spenderRule, spenderItem) + } + + logs, sub, err := _MockERC20.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) + 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(MockERC20Approval) + if err := _MockERC20.contract.UnpackLog(event, "Approval", 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 +} + +// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. +// +// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +func (_MockERC20 *MockERC20Filterer) ParseApproval(log types.Log) (*MockERC20Approval, error) { + event := new(MockERC20Approval) + if err := _MockERC20.contract.UnpackLog(event, "Approval", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// MockERC20TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the MockERC20 contract. +type MockERC20TransferIterator struct { + Event *MockERC20Transfer // 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 *MockERC20TransferIterator) 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(MockERC20Transfer) + 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(MockERC20Transfer) + 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 *MockERC20TransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *MockERC20TransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// MockERC20Transfer represents a Transfer event raised by the MockERC20 contract. +type MockERC20Transfer struct { + From common.Address + To common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_MockERC20 *MockERC20Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MockERC20TransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MockERC20.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &MockERC20TransferIterator{contract: _MockERC20.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_MockERC20 *MockERC20Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *MockERC20Transfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _MockERC20.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + 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(MockERC20Transfer) + if err := _MockERC20.contract.UnpackLog(event, "Transfer", 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 +} + +// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_MockERC20 *MockERC20Filterer) ParseTransfer(log types.Log) (*MockERC20Transfer, error) { + event := new(MockERC20Transfer) + if err := _MockERC20.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/chain/simple_custody.go b/custody/simple_custody.go similarity index 99% rename from chain/simple_custody.go rename to custody/simple_custody.go index b3acf12..5f68f9a 100644 --- a/chain/simple_custody.go +++ b/custody/simple_custody.go @@ -1,7 +1,7 @@ // Code generated - DO NOT EDIT. // This file is a generated binding and any manual changes will be lost. -package chain +package custody import ( "errors" @@ -32,7 +32,7 @@ var ( // SimpleCustodyMetaData contains all meta data concerning the SimpleCustody contract. var SimpleCustodyMetaData = &bind.MetaData{ ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"admin\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"neodax\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"nitewatch\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"DEFAULT_ADMIN_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"NEODAX_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"NITEWATCH_ROLE\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"deposit\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"finalizeWithdraw\",\"inputs\":[{\"name\":\"withdrawalId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getRoleAdmin\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"grantRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"hasRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"rejectWithdraw\",\"inputs\":[{\"name\":\"withdrawalId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"callerConfirmation\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"revokeRole\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"startWithdraw\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"nonce\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"withdrawalId\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"supportsInterface\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"withdrawals\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"user\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"finalized\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"Deposited\",\"inputs\":[{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleAdminChanged\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"previousAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"newAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleGranted\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RoleRevoked\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WithdrawFinalized\",\"inputs\":[{\"name\":\"withdrawalId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"success\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WithdrawStarted\",\"inputs\":[{\"name\":\"withdrawalId\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"user\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"token\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"nonce\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AccessControlBadConfirmation\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"AccessControlUnauthorizedAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"neededRole\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ETHTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InsufficientLiquidity\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MsgValueMismatch\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NonZeroMsgValueForERC20\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SafeERC20FailedOperation\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"WithdrawalAlreadyExists\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"WithdrawalAlreadyFinalized\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"WithdrawalNotFound\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ZeroAmount\",\"inputs\":[]}]", - Bin: "0x608060405234801561000f575f5ffd5b50604051611d9c380380611d9c833981810160405281019061003191906102c1565b600161004f6100446100d260201b60201c565b6100fb60201b60201c565b5f01819055506100675f5f1b8461010460201b60201c565b506100987f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f38361010460201b60201c565b506100c97ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa8261010460201b60201c565b50505050610311565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b5f61011583836101f960201b60201c565b6101ef5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061018c61025c60201b60201c565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600190506101f3565b5f90505b92915050565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f33905090565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61029082610267565b9050919050565b6102a081610286565b81146102aa575f5ffd5b50565b5f815190506102bb81610297565b92915050565b5f5f5f606084860312156102d8576102d7610263565b5b5f6102e5868287016102ad565b93505060206102f6868287016102ad565b9250506040610307868287016102ad565b9150509250925092565b611a7e8061031e5f395ff3fe6080604052600436106100dc575f3560e01c80635a98c2231161007e578063d547741f11610058578063d547741f146102a4578063d87e1f41146102cc578063da86f31514610308578063efbf64a714610332576100dc565b80635a98c2231461021457806391d148541461023e578063a217fddf1461027a576100dc565b8063248a9ca3116100ba578063248a9ca31461016c5780632f2ff15d146101a857806336568abe146101d057806347e7ef24146101f8576100dc565b806301ffc9a7146100e057806305e95be71461011c57806311edc78f14610144575b5f5ffd5b3480156100eb575f5ffd5b50610106600480360381019061010191906115c9565b610372565b604051610113919061160e565b60405180910390f35b348015610127575f5ffd5b50610142600480360381019061013d919061165a565b6103eb565b005b34801561014f575f5ffd5b5061016a6004803603810190610165919061165a565b6107f7565b005b348015610177575f5ffd5b50610192600480360381019061018d919061165a565b61092f565b60405161019f9190611694565b60405180910390f35b3480156101b3575f5ffd5b506101ce60048036038101906101c99190611707565b61094b565b005b3480156101db575f5ffd5b506101f660048036038101906101f19190611707565b61096d565b005b610212600480360381019061020d9190611778565b6109e8565b005b34801561021f575f5ffd5b50610228610c78565b6040516102359190611694565b60405180910390f35b348015610249575f5ffd5b50610264600480360381019061025f9190611707565b610c9c565b604051610271919061160e565b60405180910390f35b348015610285575f5ffd5b5061028e610cff565b60405161029b9190611694565b60405180910390f35b3480156102af575f5ffd5b506102ca60048036038101906102c59190611707565b610d05565b005b3480156102d7575f5ffd5b506102f260048036038101906102ed91906117b6565b610d27565b6040516102ff9190611694565b60405180910390f35b348015610313575f5ffd5b5061031c610fd6565b6040516103299190611694565b60405180910390f35b34801561033d575f5ffd5b506103586004803603810190610353919061165a565b610ffa565b604051610369959493929190611838565b60405180910390f35b5f7f7965db0b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103e457506103e382611083565b5b9050919050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa610415816110ec565b61041d611100565b5f60015f8481526020019081526020015f209050806003015f9054906101000a900460ff16610478576040517f8d0fc1dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060030160019054906101000a900460ff16156104c1576040517fae89945400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160030160016101000a81548160ff0219169083151502179055505f815f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f826001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f836002015490505f845f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f846001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f84600201819055505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106d1578047101561062c576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8373ffffffffffffffffffffffffffffffffffffffff1682604051610651906118b6565b5f6040518083038185875af1925050503d805f811461068b576040519150601f19603f3d011682016040523d82523d5f602084013e610690565b606091505b50509050806106cb576040517fb12d13eb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506107ae565b808273ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161070b91906118ca565b602060405180830381865afa158015610726573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061074a91906118f7565b1015610782576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6107ad83828473ffffffffffffffffffffffffffffffffffffffff166111229092919063ffffffff16565b5b857f150e5422471a0e0b0bf81bb0c466ec4b78850d2feeea6955c7e5eb33468a9c9c60016040516107df919061160e565b60405180910390a2505050506107f3611175565b5050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa610821816110ec565b610829611100565b5f60015f8481526020019081526020015f209050806003015f9054906101000a900460ff16610884576040517f8d0fc1dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060030160019054906101000a900460ff16156108cd576040517fae89945400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160030160016101000a81548160ff021916908315150217905550827f150e5422471a0e0b0bf81bb0c466ec4b78850d2feeea6955c7e5eb33468a9c9c5f60405161091a919061160e565b60405180910390a25061092b611175565b5050565b5f5f5f8381526020019081526020015f20600101549050919050565b6109548261092f565b61095d816110ec565b610967838361118f565b50505050565b610975611278565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146109d9576040517f6697b23200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109e3828261127f565b505050565b6109f0611100565b5f8103610a29576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8190505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a9e57813414610a99576040517fbc6f88c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c06565b5f3414610ad7576040517fa57ec87300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610b1191906118ca565b602060405180830381865afa158015610b2c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5091906118f7565b9050610b7f3330858773ffffffffffffffffffffffffffffffffffffffff16611368909392919063ffffffff16565b808473ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610bb991906118ca565b602060405180830381865afa158015610bd4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bf891906118f7565b610c02919061194f565b9150505b8273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a783604051610c639190611982565b60405180910390a350610c74611175565b5050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa81565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f5f1b81565b610d0e8261092f565b610d17816110ec565b610d21838361127f565b50505050565b5f7f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f3610d52816110ec565b610d5a611100565b5f8403610d93576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b463087878787604051602001610dae9695949392919061199b565b60405160208183030381529060405280519060200120915060015f8381526020019081526020015f206003015f9054906101000a900460ff1615610e1e576040517f157c65e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040518060a001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018581526020016001151581526020015f151581525060015f8481526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550604082015181600201556060820151816003015f6101000a81548160ff02191690831515021790555060808201518160030160016101000a81548160ff0219169083151502179055509050508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16837f669c87d38156449c65caf07041b1568372d50fc03f2cc46add1d68cebc2eb9898787604051610fbd9291906119fa565b60405180910390a4610fcd611175565b50949350505050565b7f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f381565b6001602052805f5260405f205f91509050805f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806002015490806003015f9054906101000a900460ff16908060030160019054906101000a900460ff16905085565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b6110fd816110f8611278565b6113bd565b50565b61110861140e565b600261111a61111561144f565b611478565b5f0181905550565b61112f8383836001611481565b61117057826040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161116791906118ca565b60405180910390fd5b505050565b600161118761118261144f565b611478565b5f0181905550565b5f61119a8383610c9c565b61126e5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061120b611278565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019050611272565b5f90505b92915050565b5f33905090565b5f61128a8383610c9c565b1561135e575f5f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055506112fb611278565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a460019050611362565b5f90505b92915050565b6113768484848460016114e3565b6113b757836040517f5274afe70000000000000000000000000000000000000000000000000000000081526004016113ae91906118ca565b60405180910390fd5b50505050565b6113c78282610c9c565b61140a5780826040517fe2517d3f000000000000000000000000000000000000000000000000000000008152600401611401929190611a21565b60405180910390fd5b5050565b611416611554565b1561144d576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b5f5f63a9059cbb60e01b9050604051815f525f1960601c86166004528460245260205f60445f5f8b5af1925060015f511483166114d55783831516156114c9573d5f823e3d81fd5b5f873b113d1516831692505b806040525050949350505050565b5f5f6323b872dd60e01b9050604051815f525f1960601c87166004525f1960601c86166024528460445260205f60645f5f8c5af1925060015f51148316611541578383151615611535573d5f823e3d81fd5b5f883b113d1516831692505b806040525f606052505095945050505050565b5f600261156761156261144f565b611478565b5f015414905090565b5f5ffd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6115a881611574565b81146115b2575f5ffd5b50565b5f813590506115c38161159f565b92915050565b5f602082840312156115de576115dd611570565b5b5f6115eb848285016115b5565b91505092915050565b5f8115159050919050565b611608816115f4565b82525050565b5f6020820190506116215f8301846115ff565b92915050565b5f819050919050565b61163981611627565b8114611643575f5ffd5b50565b5f8135905061165481611630565b92915050565b5f6020828403121561166f5761166e611570565b5b5f61167c84828501611646565b91505092915050565b61168e81611627565b82525050565b5f6020820190506116a75f830184611685565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6116d6826116ad565b9050919050565b6116e6816116cc565b81146116f0575f5ffd5b50565b5f81359050611701816116dd565b92915050565b5f5f6040838503121561171d5761171c611570565b5b5f61172a85828601611646565b925050602061173b858286016116f3565b9150509250929050565b5f819050919050565b61175781611745565b8114611761575f5ffd5b50565b5f813590506117728161174e565b92915050565b5f5f6040838503121561178e5761178d611570565b5b5f61179b858286016116f3565b92505060206117ac85828601611764565b9150509250929050565b5f5f5f5f608085870312156117ce576117cd611570565b5b5f6117db878288016116f3565b94505060206117ec878288016116f3565b93505060406117fd87828801611764565b925050606061180e87828801611764565b91505092959194509250565b611823816116cc565b82525050565b61183281611745565b82525050565b5f60a08201905061184b5f83018861181a565b611858602083018761181a565b6118656040830186611829565b61187260608301856115ff565b61187f60808301846115ff565b9695505050505050565b5f81905092915050565b50565b5f6118a15f83611889565b91506118ac82611893565b5f82019050919050565b5f6118c082611896565b9150819050919050565b5f6020820190506118dd5f83018461181a565b92915050565b5f815190506118f18161174e565b92915050565b5f6020828403121561190c5761190b611570565b5b5f611919848285016118e3565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61195982611745565b915061196483611745565b925082820390508181111561197c5761197b611922565b5b92915050565b5f6020820190506119955f830184611829565b92915050565b5f60c0820190506119ae5f830189611829565b6119bb602083018861181a565b6119c8604083018761181a565b6119d5606083018661181a565b6119e26080830185611829565b6119ef60a0830184611829565b979650505050505050565b5f604082019050611a0d5f830185611829565b611a1a6020830184611829565b9392505050565b5f604082019050611a345f83018561181a565b611a416020830184611685565b939250505056fea2646970667358221220675434c4bbddcb90fe2fd6368a14981a2f3c3f613b72899e2626a1e12ecf1b0664736f6c634300081e0033", + Bin: "0x608060405234801561000f575f5ffd5b50604051611d9c380380611d9c833981810160405281019061003191906102c1565b600161004f6100446100d260201b60201c565b6100fb60201b60201c565b5f01819055506100675f5f1b8461010460201b60201c565b506100987f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f38361010460201b60201c565b506100c97ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa8261010460201b60201c565b50505050610311565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b5f61011583836101f960201b60201c565b6101ef5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061018c61025c60201b60201c565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a4600190506101f3565b5f90505b92915050565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f33905090565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61029082610267565b9050919050565b6102a081610286565b81146102aa575f5ffd5b50565b5f815190506102bb81610297565b92915050565b5f5f5f606084860312156102d8576102d7610263565b5b5f6102e5868287016102ad565b93505060206102f6868287016102ad565b9250506040610307868287016102ad565b9150509250925092565b611a7e8061031e5f395ff3fe6080604052600436106100dc575f3560e01c80635a98c2231161007e578063d547741f11610058578063d547741f146102a4578063d87e1f41146102cc578063da86f31514610308578063efbf64a714610332576100dc565b80635a98c2231461021457806391d148541461023e578063a217fddf1461027a576100dc565b8063248a9ca3116100ba578063248a9ca31461016c5780632f2ff15d146101a857806336568abe146101d057806347e7ef24146101f8576100dc565b806301ffc9a7146100e057806305e95be71461011c57806311edc78f14610144575b5f5ffd5b3480156100eb575f5ffd5b50610106600480360381019061010191906115c9565b610372565b604051610113919061160e565b60405180910390f35b348015610127575f5ffd5b50610142600480360381019061013d919061165a565b6103eb565b005b34801561014f575f5ffd5b5061016a6004803603810190610165919061165a565b6107f7565b005b348015610177575f5ffd5b50610192600480360381019061018d919061165a565b61092f565b60405161019f9190611694565b60405180910390f35b3480156101b3575f5ffd5b506101ce60048036038101906101c99190611707565b61094b565b005b3480156101db575f5ffd5b506101f660048036038101906101f19190611707565b61096d565b005b610212600480360381019061020d9190611778565b6109e8565b005b34801561021f575f5ffd5b50610228610c78565b6040516102359190611694565b60405180910390f35b348015610249575f5ffd5b50610264600480360381019061025f9190611707565b610c9c565b604051610271919061160e565b60405180910390f35b348015610285575f5ffd5b5061028e610cff565b60405161029b9190611694565b60405180910390f35b3480156102af575f5ffd5b506102ca60048036038101906102c59190611707565b610d05565b005b3480156102d7575f5ffd5b506102f260048036038101906102ed91906117b6565b610d27565b6040516102ff9190611694565b60405180910390f35b348015610313575f5ffd5b5061031c610fd6565b6040516103299190611694565b60405180910390f35b34801561033d575f5ffd5b506103586004803603810190610353919061165a565b610ffa565b604051610369959493929190611838565b60405180910390f35b5f7f7965db0b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806103e457506103e382611083565b5b9050919050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa610415816110ec565b61041d611100565b5f60015f8481526020019081526020015f209050806003015f9054906101000a900460ff16610478576040517f8d0fc1dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060030160019054906101000a900460ff16156104c1576040517fae89945400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160030160016101000a81548160ff0219169083151502179055505f815f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f826001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f836002015490505f845f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f846001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505f84600201819055505f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106d1578047101561062c576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8373ffffffffffffffffffffffffffffffffffffffff1682604051610651906118b6565b5f6040518083038185875af1925050503d805f811461068b576040519150601f19603f3d011682016040523d82523d5f602084013e610690565b606091505b50509050806106cb576040517fb12d13eb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506107ae565b808273ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161070b91906118ca565b602060405180830381865afa158015610726573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061074a91906118f7565b1015610782576040517fbb55fd2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6107ad83828473ffffffffffffffffffffffffffffffffffffffff166111229092919063ffffffff16565b5b857f150e5422471a0e0b0bf81bb0c466ec4b78850d2feeea6955c7e5eb33468a9c9c60016040516107df919061160e565b60405180910390a2505050506107f3611175565b5050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa610821816110ec565b610829611100565b5f60015f8481526020019081526020015f209050806003015f9054906101000a900460ff16610884576040517f8d0fc1dd00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060030160019054906101000a900460ff16156108cd576040517fae89945400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018160030160016101000a81548160ff021916908315150217905550827f150e5422471a0e0b0bf81bb0c466ec4b78850d2feeea6955c7e5eb33468a9c9c5f60405161091a919061160e565b60405180910390a25061092b611175565b5050565b5f5f5f8381526020019081526020015f20600101549050919050565b6109548261092f565b61095d816110ec565b610967838361118f565b50505050565b610975611278565b73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146109d9576040517f6697b23200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6109e3828261127f565b505050565b6109f0611100565b5f8103610a29576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8190505f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a9e57813414610a99576040517fbc6f88c500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610c06565b5f3414610ad7576040517fa57ec87300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8373ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610b1191906118ca565b602060405180830381865afa158015610b2c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610b5091906118f7565b9050610b7f3330858773ffffffffffffffffffffffffffffffffffffffff16611368909392919063ffffffff16565b808473ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610bb991906118ca565b602060405180830381865afa158015610bd4573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610bf891906118f7565b610c02919061194f565b9150505b8273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8752a472e571a816aea92eec8dae9baf628e840f4929fbcc2d155e6233ff68a783604051610c639190611982565b60405180910390a350610c74611175565b5050565b7ff42609614d16e60ed8a62ea70f772fc08fb4f581d8126a6aeae13d7aee25daaa81565b5f5f5f8481526020019081526020015f205f015f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f9054906101000a900460ff16905092915050565b5f5f1b81565b610d0e8261092f565b610d17816110ec565b610d21838361127f565b50505050565b5f7f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f3610d52816110ec565b610d5a611100565b5f8403610d93576040517f1f2a200500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b463087878787604051602001610dae9695949392919061199b565b60405160208183030381529060405280519060200120915060015f8381526020019081526020015f206003015f9054906101000a900460ff1615610e1e576040517f157c65e100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040518060a001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018581526020016001151581526020015f151581525060015f8481526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550604082015181600201556060820151816003015f6101000a81548160ff02191690831515021790555060808201518160030160016101000a81548160ff0219169083151502179055509050508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16837f669c87d38156449c65caf07041b1568372d50fc03f2cc46add1d68cebc2eb9898787604051610fbd9291906119fa565b60405180910390a4610fcd611175565b50949350505050565b7f7f207140ff521d8790ff51fbcb7b65fa00c82600e052949aeb1de1aeceafd4f381565b6001602052805f5260405f205f91509050805f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690806002015490806003015f9054906101000a900460ff16908060030160019054906101000a900460ff16905085565b5f7f01ffc9a7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916149050919050565b6110fd816110f8611278565b6113bd565b50565b61110861140e565b600261111a61111561144f565b611478565b5f0181905550565b61112f8383836001611481565b61117057826040517f5274afe700000000000000000000000000000000000000000000000000000000815260040161116791906118ca565b60405180910390fd5b505050565b600161118761118261144f565b611478565b5f0181905550565b5f61119a8383610c9c565b61126e5760015f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff02191690831515021790555061120b611278565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a460019050611272565b5f90505b92915050565b5f33905090565b5f61128a8383610c9c565b1561135e575f5f5f8581526020019081526020015f205f015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f6101000a81548160ff0219169083151502179055506112fb611278565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16847ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b60405160405180910390a460019050611362565b5f90505b92915050565b6113768484848460016114e3565b6113b757836040517f5274afe70000000000000000000000000000000000000000000000000000000081526004016113ae91906118ca565b60405180910390fd5b50505050565b6113c78282610c9c565b61140a5780826040517fe2517d3f000000000000000000000000000000000000000000000000000000008152600401611401929190611a21565b60405180910390fd5b5050565b611416611554565b1561144d576040517f3ee5aeb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f005f1b905090565b5f819050919050565b5f5f63a9059cbb60e01b9050604051815f525f1960601c86166004528460245260205f60445f5f8b5af1925060015f511483166114d55783831516156114c9573d5f823e3d81fd5b5f873b113d1516831692505b806040525050949350505050565b5f5f6323b872dd60e01b9050604051815f525f1960601c87166004525f1960601c86166024528460445260205f60645f5f8c5af1925060015f51148316611541578383151615611535573d5f823e3d81fd5b5f883b113d1516831692505b806040525f606052505095945050505050565b5f600261156761156261144f565b611478565b5f015414905090565b5f5ffd5b5f7fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6115a881611574565b81146115b2575f5ffd5b50565b5f813590506115c38161159f565b92915050565b5f602082840312156115de576115dd611570565b5b5f6115eb848285016115b5565b91505092915050565b5f8115159050919050565b611608816115f4565b82525050565b5f6020820190506116215f8301846115ff565b92915050565b5f819050919050565b61163981611627565b8114611643575f5ffd5b50565b5f8135905061165481611630565b92915050565b5f6020828403121561166f5761166e611570565b5b5f61167c84828501611646565b91505092915050565b61168e81611627565b82525050565b5f6020820190506116a75f830184611685565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6116d6826116ad565b9050919050565b6116e6816116cc565b81146116f0575f5ffd5b50565b5f81359050611701816116dd565b92915050565b5f5f6040838503121561171d5761171c611570565b5b5f61172a85828601611646565b925050602061173b858286016116f3565b9150509250929050565b5f819050919050565b61175781611745565b8114611761575f5ffd5b50565b5f813590506117728161174e565b92915050565b5f5f6040838503121561178e5761178d611570565b5b5f61179b858286016116f3565b92505060206117ac85828601611764565b9150509250929050565b5f5f5f5f608085870312156117ce576117cd611570565b5b5f6117db878288016116f3565b94505060206117ec878288016116f3565b93505060406117fd87828801611764565b925050606061180e87828801611764565b91505092959194509250565b611823816116cc565b82525050565b61183281611745565b82525050565b5f60a08201905061184b5f83018861181a565b611858602083018761181a565b6118656040830186611829565b61187260608301856115ff565b61187f60808301846115ff565b9695505050505050565b5f81905092915050565b50565b5f6118a15f83611889565b91506118ac82611893565b5f82019050919050565b5f6118c082611896565b9150819050919050565b5f6020820190506118dd5f83018461181a565b92915050565b5f815190506118f18161174e565b92915050565b5f6020828403121561190c5761190b611570565b5b5f611919848285016118e3565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61195982611745565b915061196483611745565b925082820390508181111561197c5761197b611922565b5b92915050565b5f6020820190506119955f830184611829565b92915050565b5f60c0820190506119ae5f830189611829565b6119bb602083018861181a565b6119c8604083018761181a565b6119d5606083018661181a565b6119e26080830185611829565b6119ef60a0830184611829565b979650505050505050565b5f604082019050611a0d5f830185611829565b611a1a6020830184611829565b9392505050565b5f604082019050611a345f83018561181a565b611a416020830184611685565b939250505056fea26469706673582212202054cb52e1a19f531553465944e2440f89282f51db42df773bd1c267d4788c2a64736f6c634300081e0033", } // SimpleCustodyABI is the input ABI used to generate the binding from. diff --git a/interfaces.go b/custody/types.go similarity index 67% rename from interfaces.go rename to custody/types.go index e19b1a9..4d5c143 100644 --- a/interfaces.go +++ b/custody/types.go @@ -1,4 +1,4 @@ -package nitewatch +package custody import ( "context" @@ -10,8 +10,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// --- Domain Events --- - // WithdrawStartedEvent represents a confirmed WithdrawStarted event from the custody contract. type WithdrawStartedEvent struct { WithdrawalID [32]byte @@ -21,6 +19,7 @@ type WithdrawStartedEvent struct { Nonce *big.Int BlockNumber uint64 TxHash common.Hash + LogIndex uint } // WithdrawFinalizedEvent represents a confirmed WithdrawFinalized event from the custody contract. @@ -29,9 +28,18 @@ type WithdrawFinalizedEvent struct { Success bool BlockNumber uint64 TxHash common.Hash + LogIndex uint } -// --- Domain Model --- +// DepositedEvent represents a confirmed Deposited event from the custody contract. +type DepositedEvent struct { + User common.Address + Token common.Address + Amount *big.Int + BlockNumber uint64 + TxHash common.Hash + LogIndex uint +} // Withdrawal represents a recorded withdrawal for limit tracking. type Withdrawal struct { @@ -44,25 +52,25 @@ type Withdrawal struct { Timestamp time.Time } -// --- Interfaces --- - // Custody defines the write operations for the IWithdraw smart contract. -// Cage uses StartWithdraw; Nitewatch uses FinalizeWithdraw and RejectWithdraw. type Custody interface { StartWithdraw(opts *bind.TransactOpts, user common.Address, token common.Address, amount *big.Int, nonce *big.Int) (*types.Transaction, error) FinalizeWithdraw(opts *bind.TransactOpts, withdrawalId [32]byte) (*types.Transaction, error) RejectWithdraw(opts *bind.TransactOpts, withdrawalId [32]byte) (*types.Transaction, error) } -// EventListener defines the ability to subscribe to IWithdraw contract events. -// The sink channel is closed when the context is cancelled or an error occurs. +// EventListener defines the ability to subscribe to custody contract events. +// Each method blocks until the context is cancelled; callers should run them in goroutines. +// The sink channel is closed when the method returns. type EventListener interface { - WatchWithdrawStarted(ctx context.Context, sink chan<- *WithdrawStartedEvent) error - WatchWithdrawFinalized(ctx context.Context, sink chan<- *WithdrawFinalizedEvent) error + WatchWithdrawStarted(ctx context.Context, sink chan<- *WithdrawStartedEvent, fromBlock uint64, fromLogIndex uint32) + WatchWithdrawFinalized(ctx context.Context, sink chan<- *WithdrawFinalizedEvent, fromBlock uint64, fromLogIndex uint32) + WatchDeposited(ctx context.Context, sink chan<- *DepositedEvent, fromBlock uint64, fromLogIndex uint32) } // WithdrawalStore defines the storage operations for tracking withdrawals. type WithdrawalStore interface { Save(w *Withdrawal) error GetTotalWithdrawn(token common.Address, since time.Time) (*big.Int, error) + GetTotalWithdrawnByUser(user common.Address, token common.Address, since time.Time) (*big.Int, error) } diff --git a/go.mod b/go.mod index 359d963..ac4febc 100644 --- a/go.mod +++ b/go.mod @@ -4,99 +4,112 @@ go 1.25.6 require ( github.com/ethereum/go-ethereum v1.16.8 + github.com/gin-gonic/gin v1.10.0 + github.com/ipfs/go-log/v2 v2.9.1 + github.com/layer-3/clearsync v0.0.129 + github.com/stretchr/testify v1.11.1 + github.com/testcontainers/testcontainers-go v0.40.0 + golang.org/x/sync v0.18.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 ) require ( - github.com/DataDog/zstd v1.4.5 // indirect + dario.cat/mergo v1.0.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect - github.com/VictoriaMetrics/fastcache v1.13.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cockroachdb/errors v1.11.3 // indirect - github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect - github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect - github.com/cockroachdb/pebble v1.1.5 // indirect - github.com/cockroachdb/redact v1.1.5 // indirect - github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v0.2.1 // indirect + github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dchest/siphash v1.2.3 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect - github.com/emicklei/dot v1.6.2 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/docker v28.5.1+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/ebitengine/purego v0.8.4 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect - github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect - github.com/ferranbt/fastssz v0.1.4 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/gofrs/flock v0.12.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v1.0.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gorilla/websocket v1.4.2 // indirect - github.com/hashicorp/go-bexpr v0.1.10 // indirect - github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db // indirect - github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/gorilla/websocket v1.5.0 // indirect github.com/holiman/uint256 v1.3.2 // indirect - github.com/huin/goupnp v1.3.0 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect - github.com/klauspost/compress v1.16.0 // indirect - github.com/klauspost/cpuid/v2 v2.0.9 // indirect - github.com/kr/pretty v0.3.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/mitchellh/pointerstructure v1.2.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pion/dtls/v2 v2.2.7 // indirect - github.com/pion/logging v0.2.2 // indirect - github.com/pion/stun/v2 v2.0.0 // indirect - github.com/pion/transport/v2 v2.2.1 // indirect - github.com/pion/transport/v3 v3.0.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.15.0 // indirect - github.com/prometheus/client_model v0.3.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.7.0 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect + github.com/shirou/gopsutil/v4 v4.25.6 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect - github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect - github.com/urfave/cli/v2 v2.27.5 // indirect - github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/crypto v0.44.0 // indirect - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.45.0 // indirect + golang.org/x/sys v0.40.0 // indirect + golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index f007a0b..4cbcfbd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,11 @@ -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 h1:1zYrtlhrZ6/b6SAjLSfKzWtdgqK0U+HtH/VcBWh1BaU= @@ -8,18 +14,24 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= -github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= @@ -34,13 +46,24 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -48,12 +71,22 @@ github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= @@ -64,53 +97,65 @@ github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9i github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= @@ -119,7 +164,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= @@ -128,33 +172,42 @@ github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSH github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/ipfs/go-log/v2 v2.9.1 h1:3JXwHWU31dsCpvQ+7asz6/QsFJHqFr4gLgQ0FWteujk= +github.com/ipfs/go-log/v2 v2.9.1/go.mod h1:evFx7sBiohUN3AG12mXlZBw5hacBQld3ZPHrowlJYoo= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4= -github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/layer-3/clearsync v0.0.129 h1:lB/KrxdHvWoG/0QJSwYXf4vGAgG4Z7NcYkrbpnCiIow= +github.com/layer-3/clearsync v0.0.129/go.mod h1:s/bT4t3kAK+ZrRtUclhY13kpyurOF71/lCdQ5BOwNBI= github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -167,23 +220,41 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= -github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -194,169 +265,136 @@ github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= -github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= +github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOGfUAD54q1s2YBcBz/WcsxCO9HUQ4aGV5hUw= github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= +github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= -golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -366,3 +404,7 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/checker/checker.go b/internal/checker/checker.go new file mode 100644 index 0000000..e92a7c1 --- /dev/null +++ b/internal/checker/checker.go @@ -0,0 +1,151 @@ +package checker + +import ( + "errors" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + + "github.com/layer-3/nitewatch/custody" +) + +var ( + ErrNoLimitsConfigured = errors.New("no limits configured for token") + ErrHourlyLimitExceeded = errors.New("hourly limit exceeded") + ErrDailyLimitExceeded = errors.New("daily limit exceeded") + ErrUserHourlyLimitExceeded = errors.New("per-user hourly limit exceeded") + ErrUserDailyLimitExceeded = errors.New("per-user daily limit exceeded") + ErrInvalidAmount = errors.New("amount must be positive") + ErrInvalidUser = errors.New("user address must not be zero") +) + +type Limit struct { + Hourly *big.Int + Daily *big.Int +} + +type Checker struct { + globalLimits map[common.Address]Limit + userOverrides map[common.Address]map[common.Address]Limit + store custody.WithdrawalStore + nowFunc func() time.Time +} + +func New( + globalLimits map[common.Address]Limit, + userOverrides map[common.Address]map[common.Address]Limit, + store custody.WithdrawalStore, +) *Checker { + return &Checker{ + globalLimits: globalLimits, + userOverrides: userOverrides, + store: store, + nowFunc: time.Now, + } +} + +func (c *Checker) Check(user common.Address, token common.Address, amount *big.Int) error { + if amount.Sign() <= 0 { + return ErrInvalidAmount + } + if user == (common.Address{}) { + return ErrInvalidUser + } + + if err := c.checkGlobalLimits(token, amount); err != nil { + return err + } + + if err := c.checkUserLimits(user, token, amount); err != nil { + return err + } + + return nil +} + +func (c *Checker) checkGlobalLimits(token common.Address, amount *big.Int) error { + l, ok := c.globalLimits[token] + if !ok { + return fmt.Errorf("%w: %s", ErrNoLimitsConfigured, token.Hex()) + } + + now := c.nowFunc() + + if l.Hourly != nil { + startOfHour := now.Truncate(time.Hour) + total, err := c.store.GetTotalWithdrawn(token, startOfHour) + if err != nil { + return fmt.Errorf("failed to get hourly withdrawn amount: %w", err) + } + newTotal := new(big.Int).Add(total, amount) + if newTotal.Cmp(l.Hourly) > 0 { + return fmt.Errorf("%w for %s: %s > %s", ErrHourlyLimitExceeded, token.Hex(), newTotal, l.Hourly) + } + } + + if l.Daily != nil { + startOfDay := now.Truncate(24 * time.Hour) + total, err := c.store.GetTotalWithdrawn(token, startOfDay) + if err != nil { + return fmt.Errorf("failed to get daily withdrawn amount: %w", err) + } + newTotal := new(big.Int).Add(total, amount) + if newTotal.Cmp(l.Daily) > 0 { + return fmt.Errorf("%w for %s: %s > %s", ErrDailyLimitExceeded, token.Hex(), newTotal, l.Daily) + } + } + + return nil +} + +func (c *Checker) resolveUserLimit(user, token common.Address) *Limit { + if userTokens, ok := c.userOverrides[user]; ok { + if l, ok := userTokens[token]; ok { + return &l + } + } + return nil +} + +func (c *Checker) checkUserLimits(user, token common.Address, amount *big.Int) error { + l := c.resolveUserLimit(user, token) + if l == nil { + return nil + } + + now := c.nowFunc() + + if l.Hourly != nil { + startOfHour := now.Truncate(time.Hour) + total, err := c.store.GetTotalWithdrawnByUser(user, token, startOfHour) + if err != nil { + return fmt.Errorf("failed to get per-user hourly withdrawn amount: %w", err) + } + newTotal := new(big.Int).Add(total, amount) + if newTotal.Cmp(l.Hourly) > 0 { + return fmt.Errorf("%w for user %s token %s: %s > %s", + ErrUserHourlyLimitExceeded, user.Hex(), token.Hex(), newTotal, l.Hourly) + } + } + + if l.Daily != nil { + startOfDay := now.Truncate(24 * time.Hour) + total, err := c.store.GetTotalWithdrawnByUser(user, token, startOfDay) + if err != nil { + return fmt.Errorf("failed to get per-user daily withdrawn amount: %w", err) + } + newTotal := new(big.Int).Add(total, amount) + if newTotal.Cmp(l.Daily) > 0 { + return fmt.Errorf("%w for user %s token %s: %s > %s", + ErrUserDailyLimitExceeded, user.Hex(), token.Hex(), newTotal, l.Daily) + } + } + + return nil +} + +func (c *Checker) Record(w *custody.Withdrawal) error { + return c.store.Save(w) +} diff --git a/internal/checker/checker_test.go b/internal/checker/checker_test.go new file mode 100644 index 0000000..495cccc --- /dev/null +++ b/internal/checker/checker_test.go @@ -0,0 +1,281 @@ +package checker + +import ( + "errors" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitewatch/custody" +) + +type mockStore struct { + withdrawals []*custody.Withdrawal + err error +} + +func (m *mockStore) Save(w *custody.Withdrawal) error { + m.withdrawals = append(m.withdrawals, w) + return nil +} + +func (m *mockStore) GetTotalWithdrawn(token common.Address, since time.Time) (*big.Int, error) { + if m.err != nil { + return nil, m.err + } + total := new(big.Int) + for _, w := range m.withdrawals { + if w.Token == token && !w.Timestamp.Before(since) { + total.Add(total, w.Amount) + } + } + return total, nil +} + +func (m *mockStore) GetTotalWithdrawnByUser(user common.Address, token common.Address, since time.Time) (*big.Int, error) { + if m.err != nil { + return nil, m.err + } + total := new(big.Int) + for _, w := range m.withdrawals { + if w.User == user && w.Token == token && !w.Timestamp.Before(since) { + total.Add(total, w.Amount) + } + } + return total, nil +} + +var ( + tokenA = common.HexToAddress("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") + tokenB = common.HexToAddress("0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB") + userA = common.HexToAddress("0x1111111111111111111111111111111111111111") + userB = common.HexToAddress("0x2222222222222222222222222222222222222222") +) + +func globalLimits(token common.Address, hourly, daily *big.Int) map[common.Address]Limit { + return map[common.Address]Limit{ + token: {Hourly: hourly, Daily: daily}, + } +} + +func TestNew_Valid(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, &mockStore{}) + require.NotNil(t, c) +} + +func TestCheck_SanityChecks(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, &mockStore{}) + + t.Run("zero amount", func(t *testing.T) { + err := c.Check(userA, tokenA, big.NewInt(0)) + require.ErrorIs(t, err, ErrInvalidAmount) + }) + + t.Run("negative amount", func(t *testing.T) { + err := c.Check(userA, tokenA, big.NewInt(-1)) + require.ErrorIs(t, err, ErrInvalidAmount) + }) + + t.Run("zero user address", func(t *testing.T) { + err := c.Check(common.Address{}, tokenA, big.NewInt(100)) + require.ErrorIs(t, err, ErrInvalidUser) + }) +} + +func TestCheck_NoLimitsConfigured(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), nil), nil, &mockStore{}) + + err := c.Check(userA, tokenB, big.NewInt(100)) + require.ErrorIs(t, err, ErrNoLimitsConfigured) +} + +func TestCheck_UnderHourlyLimit(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, &mockStore{}) + c.nowFunc = func() time.Time { + return time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) + } + + require.NoError(t, c.Check(userA, tokenA, big.NewInt(500))) +} + +func TestCheck_ExactHourlyLimit(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, &mockStore{}) + c.nowFunc = func() time.Time { + return time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) + } + + require.NoError(t, c.Check(userA, tokenA, big.NewInt(1000))) +} + +func TestCheck_ExceedHourlyLimit(t *testing.T) { + now := time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) + store := &mockStore{ + withdrawals: []*custody.Withdrawal{ + {Token: tokenA, User: userA, Amount: big.NewInt(800), Timestamp: now.Add(-10 * time.Minute)}, + }, + } + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, store) + c.nowFunc = func() time.Time { return now } + + err := c.Check(userA, tokenA, big.NewInt(300)) + require.ErrorIs(t, err, ErrHourlyLimitExceeded) +} + +func TestCheck_ExceedDailyLimit(t *testing.T) { + now := time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) + store := &mockStore{ + withdrawals: []*custody.Withdrawal{ + {Token: tokenA, User: userA, Amount: big.NewInt(4500), Timestamp: now.Add(-3 * time.Hour)}, + }, + } + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, store) + c.nowFunc = func() time.Time { return now } + + err := c.Check(userA, tokenA, big.NewInt(600)) + require.ErrorIs(t, err, ErrDailyLimitExceeded) +} + +func TestCheck_PreviousHourNotCounted(t *testing.T) { + now := time.Date(2025, 1, 1, 13, 5, 0, 0, time.UTC) + store := &mockStore{ + withdrawals: []*custody.Withdrawal{ + {Token: tokenA, User: userA, Amount: big.NewInt(900), Timestamp: time.Date(2025, 1, 1, 12, 50, 0, 0, time.UTC)}, + }, + } + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, store) + c.nowFunc = func() time.Time { return now } + + require.NoError(t, c.Check(userA, tokenA, big.NewInt(900))) +} + +func TestCheck_HourlyOnlyConfig(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), nil), nil, &mockStore{}) + c.nowFunc = func() time.Time { + return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + } + + require.NoError(t, c.Check(userA, tokenA, big.NewInt(999))) +} + +func TestCheck_DailyOnlyConfig(t *testing.T) { + c := New(globalLimits(tokenA, nil, big.NewInt(5000)), nil, &mockStore{}) + c.nowFunc = func() time.Time { + return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + } + + require.NoError(t, c.Check(userA, tokenA, big.NewInt(4000))) +} + +func TestCheck_StoreError(t *testing.T) { + store := &mockStore{err: errors.New("db connection lost")} + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, store) + c.nowFunc = func() time.Time { + return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + } + + err := c.Check(userA, tokenA, big.NewInt(100)) + require.Error(t, err) +} + +func TestRecord(t *testing.T) { + store := &mockStore{} + c := New(globalLimits(tokenA, big.NewInt(1000), nil), nil, store) + + w := &custody.Withdrawal{ + WithdrawalID: [32]byte{1}, + User: userA, + Token: tokenA, + Amount: big.NewInt(500), + Timestamp: time.Now(), + } + require.NoError(t, c.Record(w)) + require.Len(t, store.withdrawals, 1) +} + +// --- Per-user limit tests --- + +func TestCheck_PerUserOverrideTakesPrecedence(t *testing.T) { + now := time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) + store := &mockStore{ + withdrawals: []*custody.Withdrawal{ + {Token: tokenA, User: userA, Amount: big.NewInt(800), Timestamp: now.Add(-10 * time.Minute)}, + }, + } + + global := globalLimits(tokenA, big.NewInt(10000), big.NewInt(50000)) + overrides := map[common.Address]map[common.Address]Limit{ + userA: {tokenA: {Hourly: big.NewInt(1000), Daily: big.NewInt(5000)}}, + } + + c := New(global, overrides, store) + c.nowFunc = func() time.Time { return now } + + // userA has override of 1000 hourly; 800 + 150 = 950 < 1000 → pass + require.NoError(t, c.Check(userA, tokenA, big.NewInt(150))) + + // userA: 800 + 250 = 1050 > 1000 → per-user hourly exceeded + err := c.Check(userA, tokenA, big.NewInt(250)) + require.ErrorIs(t, err, ErrUserHourlyLimitExceeded) +} + +func TestCheck_PerUserLimitIndependentOfGlobal(t *testing.T) { + now := time.Date(2025, 1, 1, 12, 30, 0, 0, time.UTC) + + store := &mockStore{ + withdrawals: []*custody.Withdrawal{ + {Token: tokenA, User: userA, Amount: big.NewInt(400), Timestamp: now.Add(-10 * time.Minute)}, + {Token: tokenA, User: userB, Amount: big.NewInt(400), Timestamp: now.Add(-5 * time.Minute)}, + }, + } + + global := globalLimits(tokenA, big.NewInt(1000), nil) + overrides := map[common.Address]map[common.Address]Limit{ + userA: {tokenA: {Hourly: big.NewInt(500)}}, + userB: {tokenA: {Hourly: big.NewInt(500)}}, + } + + c := New(global, overrides, store) + c.nowFunc = func() time.Time { return now } + + // userA: 400 + 200 = 600 > per-user 500 → blocked by per-user limit + err := c.Check(userA, tokenA, big.NewInt(200)) + require.ErrorIs(t, err, ErrUserHourlyLimitExceeded) + + // global: 800 + 250 = 1050 > 1000 → blocked by global limit + err = c.Check(userB, tokenA, big.NewInt(250)) + require.Error(t, err) +} + +func TestCheck_PerUserDailyLimitExceeded(t *testing.T) { + now := time.Date(2025, 1, 1, 18, 30, 0, 0, time.UTC) + store := &mockStore{ + withdrawals: []*custody.Withdrawal{ + {Token: tokenA, User: userA, Amount: big.NewInt(1500), Timestamp: now.Add(-6 * time.Hour)}, + {Token: tokenA, User: userA, Amount: big.NewInt(400), Timestamp: now.Add(-2 * time.Hour)}, + }, + } + + global := globalLimits(tokenA, big.NewInt(10000), big.NewInt(50000)) + overrides := map[common.Address]map[common.Address]Limit{ + userA: {tokenA: {Hourly: big.NewInt(5000), Daily: big.NewInt(2000)}}, + } + + c := New(global, overrides, store) + c.nowFunc = func() time.Time { return now } + + // userA already withdrew 1500+400=1900 today; 1900+200=2100 > daily limit 2000 + err := c.Check(userA, tokenA, big.NewInt(200)) + require.ErrorIs(t, err, ErrUserDailyLimitExceeded) +} + +func TestCheck_NoPerUserLimitsConfigured(t *testing.T) { + c := New(globalLimits(tokenA, big.NewInt(1000), big.NewInt(5000)), nil, &mockStore{}) + c.nowFunc = func() time.Time { + return time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + } + + require.NoError(t, c.Check(userA, tokenA, big.NewInt(500))) +} diff --git a/internal/store/adapter.go b/internal/store/adapter.go new file mode 100644 index 0000000..4cc6197 --- /dev/null +++ b/internal/store/adapter.go @@ -0,0 +1,139 @@ +package store + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "gorm.io/gorm" + "gorm.io/gorm/clause" + + "github.com/layer-3/nitewatch/custody" +) + +type WithdrawalModel struct { + gorm.Model + WithdrawalID string `gorm:"uniqueIndex;type:varchar(66)"` + User string `gorm:"index;type:varchar(42)"` + Token string `gorm:"index;type:varchar(42)"` + Amount string `gorm:"type:text"` + BlockNumber uint64 + TxHash string `gorm:"type:varchar(66)"` + Timestamp time.Time `gorm:"index"` +} + +type BlockCursorModel struct { + StreamName string `gorm:"primaryKey;type:varchar(64)"` + BlockNumber uint64 `gorm:"not null"` + LogIndex uint `gorm:"not null"` + UpdatedAt time.Time `gorm:"not null;autoUpdateTime"` +} + +type WithdrawEventModel struct { + ID uint64 `gorm:"primaryKey;autoIncrement"` + WithdrawalID string `gorm:"type:varchar(66);not null;uniqueIndex"` + UserAddress string `gorm:"type:varchar(42);not null"` + TokenAddress string `gorm:"type:varchar(42);not null"` + Amount string `gorm:"type:text;not null"` + Decision string `gorm:"type:varchar(16);not null"` + Reason string `gorm:"type:text;not null;default:''"` + BlockNumber uint64 `gorm:"not null"` + TxHash string `gorm:"type:varchar(66);not null"` + LogIndex uint `gorm:"not null"` + CreatedAt time.Time `gorm:"not null;autoCreateTime"` +} + +type Adapter struct { + db *gorm.DB +} + +func NewAdapter(db *gorm.DB) (*Adapter, error) { + if err := db.AutoMigrate(&WithdrawalModel{}, &BlockCursorModel{}, &WithdrawEventModel{}); err != nil { + return nil, err + } + return &Adapter{db: db}, nil +} + +var _ custody.WithdrawalStore = (*Adapter)(nil) + +func (a *Adapter) Save(w *custody.Withdrawal) error { + model := &WithdrawalModel{ + WithdrawalID: common.Hash(w.WithdrawalID).Hex(), + User: w.User.Hex(), + Token: w.Token.Hex(), + Amount: w.Amount.String(), + BlockNumber: w.BlockNumber, + TxHash: w.TxHash.Hex(), + Timestamp: w.Timestamp, + } + return a.db.Create(model).Error +} + +func (a *Adapter) GetTotalWithdrawn(token common.Address, since time.Time) (*big.Int, error) { + var withdrawals []WithdrawalModel + if err := a.db.Where("token = ? AND timestamp >= ?", token.Hex(), since).Find(&withdrawals).Error; err != nil { + return nil, err + } + return sumAmounts(withdrawals) +} + +func (a *Adapter) GetTotalWithdrawnByUser(user, token common.Address, since time.Time) (*big.Int, error) { + var withdrawals []WithdrawalModel + if err := a.db.Where("user = ? AND token = ? AND timestamp >= ?", user.Hex(), token.Hex(), since).Find(&withdrawals).Error; err != nil { + return nil, err + } + return sumAmounts(withdrawals) +} + +func sumAmounts(withdrawals []WithdrawalModel) (*big.Int, error) { + total := new(big.Int) + for _, w := range withdrawals { + amount, ok := new(big.Int).SetString(w.Amount, 10) + if !ok { + return nil, fmt.Errorf("corrupted amount in withdrawal %s: %q", w.WithdrawalID, w.Amount) + } + total.Add(total, amount) + } + return total, nil +} + +func (a *Adapter) GetCursor(streamName string) (blockNumber uint64, logIndex uint32, err error) { + var cursor BlockCursorModel + result := a.db.Where("stream_name = ?", streamName).First(&cursor) + if result.Error != nil { + if result.Error == gorm.ErrRecordNotFound { + return 0, 0, nil + } + return 0, 0, result.Error + } + return cursor.BlockNumber, uint32(cursor.LogIndex), nil +} + +func (a *Adapter) RecordWithdrawEvent(ev *WithdrawEventModel) error { + return a.db.Transaction(func(tx *gorm.DB) error { + result := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(ev) + if result.Error != nil { + return result.Error + } + return upsertCursor(tx, "withdraw_started", ev.BlockNumber, ev.LogIndex) + }) +} + +func (a *Adapter) HasWithdrawEvent(withdrawalID string) bool { + var count int64 + a.db.Model(&WithdrawEventModel{}).Where("withdrawal_id = ?", withdrawalID).Count(&count) + return count > 0 +} + +func upsertCursor(tx *gorm.DB, streamName string, blockNumber uint64, logIndex uint) error { + cursor := BlockCursorModel{ + StreamName: streamName, + BlockNumber: blockNumber, + LogIndex: logIndex, + } + return tx.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "stream_name"}}, + DoUpdates: clause.AssignmentColumns([]string{"block_number", "log_index", "updated_at"}), + }).Create(&cursor).Error +} diff --git a/store/adapter_test.go b/internal/store/adapter_test.go similarity index 56% rename from store/adapter_test.go rename to internal/store/adapter_test.go index f6037f8..9c24558 100644 --- a/store/adapter_test.go +++ b/internal/store/adapter_test.go @@ -6,11 +6,12 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" - nw "github.com/layer-3/nitewatch" + "github.com/layer-3/nitewatch/custody" ) func newTestAdapter(t *testing.T) *Adapter { @@ -18,13 +19,9 @@ func newTestAdapter(t *testing.T) *Adapter { db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{ Logger: logger.Discard, }) - if err != nil { - t.Fatalf("failed to open in-memory db: %v", err) - } + require.NoError(t, err) adapter, err := NewAdapter(db) - if err != nil { - t.Fatalf("failed to create adapter: %v", err) - } + require.NoError(t, err) return adapter } @@ -37,7 +34,7 @@ var ( func TestSave(t *testing.T) { a := newTestAdapter(t) - w := &nw.Withdrawal{ + w := &custody.Withdrawal{ WithdrawalID: [32]byte{1}, User: user, Token: tokenA, @@ -47,24 +44,17 @@ func TestSave(t *testing.T) { Timestamp: time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC), } - if err := a.Save(w); err != nil { - t.Fatalf("Save failed: %v", err) - } + require.NoError(t, a.Save(w)) - // Verify it was persisted total, err := a.GetTotalWithdrawn(tokenA, time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)) - if err != nil { - t.Fatalf("GetTotalWithdrawn failed: %v", err) - } - if total.Cmp(big.NewInt(1000)) != 0 { - t.Fatalf("expected total 1000, got %s", total) - } + require.NoError(t, err) + require.Equal(t, "1000", total.String()) } func TestSave_DuplicateWithdrawalID(t *testing.T) { a := newTestAdapter(t) - w := &nw.Withdrawal{ + w := &custody.Withdrawal{ WithdrawalID: [32]byte{1}, User: user, Token: tokenA, @@ -72,24 +62,16 @@ func TestSave_DuplicateWithdrawalID(t *testing.T) { Timestamp: time.Now(), } - if err := a.Save(w); err != nil { - t.Fatalf("first Save failed: %v", err) - } - if err := a.Save(w); err == nil { - t.Fatal("expected error on duplicate withdrawal ID") - } + require.NoError(t, a.Save(w)) + require.Error(t, a.Save(w)) } func TestGetTotalWithdrawn_Empty(t *testing.T) { a := newTestAdapter(t) total, err := a.GetTotalWithdrawn(tokenA, time.Time{}) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if total.Sign() != 0 { - t.Fatalf("expected zero, got %s", total) - } + require.NoError(t, err) + require.Equal(t, 0, total.Sign()) } func TestGetTotalWithdrawn_TimeFilter(t *testing.T) { @@ -97,46 +79,26 @@ func TestGetTotalWithdrawn_TimeFilter(t *testing.T) { base := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) - withdrawals := []*nw.Withdrawal{ + withdrawals := []*custody.Withdrawal{ {WithdrawalID: [32]byte{1}, User: user, Token: tokenA, Amount: big.NewInt(100), Timestamp: base.Add(-2 * time.Hour)}, {WithdrawalID: [32]byte{2}, User: user, Token: tokenA, Amount: big.NewInt(200), Timestamp: base.Add(-30 * time.Minute)}, {WithdrawalID: [32]byte{3}, User: user, Token: tokenA, Amount: big.NewInt(300), Timestamp: base.Add(10 * time.Minute)}, } for _, w := range withdrawals { - if err := a.Save(w); err != nil { - t.Fatalf("Save failed: %v", err) - } + require.NoError(t, a.Save(w)) } - // Since base: should include w2 (at base-30m? No, base-30m < base) and w3 - // Actually base-30m is before base, so only w3 is >= base - // Let me check: since = base = 12:00 - // w1 = 10:00 (before), w2 = 11:30 (before), w3 = 12:10 (after) total, err := a.GetTotalWithdrawn(tokenA, base) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if total.Cmp(big.NewInt(300)) != 0 { - t.Fatalf("expected 300, got %s", total) - } + require.NoError(t, err) + require.Equal(t, "300", total.String()) - // Since 11:00: should include w2 and w3 total, err = a.GetTotalWithdrawn(tokenA, base.Add(-1*time.Hour)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if total.Cmp(big.NewInt(500)) != 0 { - t.Fatalf("expected 500, got %s", total) - } + require.NoError(t, err) + require.Equal(t, "500", total.String()) - // Since beginning: all three total, err = a.GetTotalWithdrawn(tokenA, base.Add(-3*time.Hour)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if total.Cmp(big.NewInt(600)) != 0 { - t.Fatalf("expected 600, got %s", total) - } + require.NoError(t, err) + require.Equal(t, "600", total.String()) } func TestGetTotalWithdrawn_TokenFilter(t *testing.T) { @@ -144,57 +106,77 @@ func TestGetTotalWithdrawn_TokenFilter(t *testing.T) { base := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) - withdrawals := []*nw.Withdrawal{ + withdrawals := []*custody.Withdrawal{ {WithdrawalID: [32]byte{1}, User: user, Token: tokenA, Amount: big.NewInt(100), Timestamp: base}, {WithdrawalID: [32]byte{2}, User: user, Token: tokenB, Amount: big.NewInt(200), Timestamp: base}, {WithdrawalID: [32]byte{3}, User: user, Token: tokenA, Amount: big.NewInt(300), Timestamp: base}, } for _, w := range withdrawals { - if err := a.Save(w); err != nil { - t.Fatalf("Save failed: %v", err) - } + require.NoError(t, a.Save(w)) } totalA, err := a.GetTotalWithdrawn(tokenA, base.Add(-time.Hour)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if totalA.Cmp(big.NewInt(400)) != 0 { - t.Fatalf("expected 400 for tokenA, got %s", totalA) - } + require.NoError(t, err) + require.Equal(t, "400", totalA.String()) totalB, err := a.GetTotalWithdrawn(tokenB, base.Add(-time.Hour)) - if err != nil { - t.Fatalf("unexpected error: %v", err) + require.NoError(t, err) + require.Equal(t, "200", totalB.String()) +} + +func TestGetTotalWithdrawnByUser(t *testing.T) { + a := newTestAdapter(t) + base := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) + + userB := common.HexToAddress("0x2222222222222222222222222222222222222222") + + withdrawals := []*custody.Withdrawal{ + {WithdrawalID: [32]byte{1}, User: user, Token: tokenA, Amount: big.NewInt(100), Timestamp: base}, + {WithdrawalID: [32]byte{2}, User: user, Token: tokenA, Amount: big.NewInt(200), Timestamp: base}, + {WithdrawalID: [32]byte{3}, User: userB, Token: tokenA, Amount: big.NewInt(300), Timestamp: base}, + {WithdrawalID: [32]byte{4}, User: user, Token: tokenB, Amount: big.NewInt(400), Timestamp: base}, } - if totalB.Cmp(big.NewInt(200)) != 0 { - t.Fatalf("expected 200 for tokenB, got %s", totalB) + for _, w := range withdrawals { + require.NoError(t, a.Save(w)) } + + // user + tokenA = 100 + 200 = 300 + total, err := a.GetTotalWithdrawnByUser(user, tokenA, base.Add(-time.Hour)) + require.NoError(t, err) + require.Equal(t, "300", total.String()) + + // userB + tokenA = 300 + total, err = a.GetTotalWithdrawnByUser(userB, tokenA, base.Add(-time.Hour)) + require.NoError(t, err) + require.Equal(t, "300", total.String()) + + // user + tokenB = 400 + total, err = a.GetTotalWithdrawnByUser(user, tokenB, base.Add(-time.Hour)) + require.NoError(t, err) + require.Equal(t, "400", total.String()) + + // userB + tokenB = 0 (no withdrawals) + total, err = a.GetTotalWithdrawnByUser(userB, tokenB, base.Add(-time.Hour)) + require.NoError(t, err) + require.Equal(t, 0, total.Sign()) } func TestGetTotalWithdrawn_LargeAmounts(t *testing.T) { a := newTestAdapter(t) base := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) - // Use amounts larger than uint64 bigAmount, _ := new(big.Int).SetString("999999999999999999999999999999", 10) - w := &nw.Withdrawal{ + w := &custody.Withdrawal{ WithdrawalID: [32]byte{1}, User: user, Token: tokenA, Amount: bigAmount, Timestamp: base, } - if err := a.Save(w); err != nil { - t.Fatalf("Save failed: %v", err) - } + require.NoError(t, a.Save(w)) total, err := a.GetTotalWithdrawn(tokenA, base.Add(-time.Hour)) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if total.Cmp(bigAmount) != 0 { - t.Fatalf("expected %s, got %s", bigAmount, total) - } + require.NoError(t, err) + require.Equal(t, bigAmount.String(), total.String()) } diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..a0da734 --- /dev/null +++ b/service/service.go @@ -0,0 +1,344 @@ +package service + +import ( + "context" + "fmt" + "log/slog" + "math/big" + "net/http" + "os" + "os/signal" + "sync/atomic" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/gin-gonic/gin" + "golang.org/x/sync/errgroup" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + + "github.com/layer-3/nitewatch/config" + "github.com/layer-3/nitewatch/custody" + "github.com/layer-3/nitewatch/internal/checker" + "github.com/layer-3/nitewatch/internal/store" +) + +type httpServer struct { + Engine *gin.Engine + server *http.Server +} + +func newHTTPServer(addr string) *httpServer { + engine := gin.New() + engine.Use(gin.Recovery()) + return &httpServer{ + Engine: engine, + server: &http.Server{Addr: addr, Handler: engine}, + } +} + +func (s *httpServer) Run() error { return s.server.ListenAndServe() } +func (s *httpServer) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } + +type Service struct { + Config config.Config + Logger *slog.Logger + + web *httpServer + ethClient *ethclient.Client + contract *custody.IWithdraw + listener *custody.Listener + auth *bind.TransactOpts + checker *checker.Checker + store *store.Adapter + + workerReady int32 +} + +func New(conf config.Config) (*Service, error) { + logger := slog.New(slog.NewTextHandler(os.Stderr, nil)).With("service", "nitewatch") + + srv := newHTTPServer(conf.ListenAddr) + + gormDB, err := gorm.Open(sqlite.Open(conf.DBPath), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + + db, err := store.NewAdapter(gormDB) + if err != nil { + return nil, fmt.Errorf("failed to initialize database: %w", err) + } + + globalLimits, err := parseLimitsConfig(conf.Limits) + if err != nil { + return nil, fmt.Errorf("failed to parse global limits: %w", err) + } + + userOverrides, err := parseUserOverrides(conf.PerUserOverrides) + if err != nil { + return nil, fmt.Errorf("failed to parse per-user overrides: %w", err) + } + + chk := checker.New(globalLimits, userOverrides, db) + + client, err := ethclient.Dial(conf.Blockchain.RPCURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to Ethereum RPC: %w", err) + } + + chainID, err := client.ChainID(context.Background()) + if err != nil { + client.Close() + return nil, fmt.Errorf("failed to get chain ID: %w", err) + } + + key, err := crypto.HexToECDSA(conf.Blockchain.PrivateKey) + if err != nil { + client.Close() + return nil, fmt.Errorf("failed to parse private key: %w", err) + } + + auth, err := bind.NewKeyedTransactorWithChainID(key, chainID) + if err != nil { + client.Close() + return nil, fmt.Errorf("failed to create transactor: %w", err) + } + + addr := common.HexToAddress(conf.Blockchain.ContractAddr) + withdrawContract, err := custody.NewIWithdraw(addr, client) + if err != nil { + client.Close() + return nil, fmt.Errorf("failed to bind IWithdraw contract: %w", err) + } + + listener := custody.NewListener(client, addr, withdrawContract, nil) + + return &Service{ + Config: conf, + Logger: logger, + web: srv, + ethClient: client, + contract: withdrawContract, + listener: listener, + auth: auth, + checker: chk, + store: db, + }, nil +} + +func (svc *Service) IsWorkerReady() bool { + return atomic.LoadInt32(&svc.workerReady) == 1 +} + +func (svc *Service) setWorkerReady() { + atomic.StoreInt32(&svc.workerReady, 1) +} + +func (svc *Service) RunWorker() error { + return svc.RunWorkerWithContext(context.Background()) +} + +func (svc *Service) RunWorkerWithContext(ctx context.Context) error { + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + + attachWebHandlers(svc) + + g, ctx := errgroup.WithContext(ctx) + + g.Go(func() error { + svc.Logger.Info("Starting health endpoint server") + return svc.web.Run() + }) + + g.Go(func() error { + fromBlock, fromLogIdx, err := svc.store.GetCursor("withdraw_started") + if err != nil { + svc.Logger.Warn("Failed to read withdraw_started cursor, starting from head", "error", err) + } + svc.Logger.Info("Starting WithdrawStarted event watcher", "from_block", fromBlock, "from_log_index", fromLogIdx) + withdrawals := make(chan *custody.WithdrawStartedEvent) + go svc.listener.WatchWithdrawStarted(ctx, withdrawals, fromBlock, fromLogIdx) + for event := range withdrawals { + svc.processWithdrawal(ctx, event) + } + return nil + }) + + g.Go(func() error { + <-ctx.Done() + svc.Logger.Info("Shutting down health endpoint server") + ctxShutdown, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second) + defer cancelShutdown() + return svc.web.Shutdown(ctxShutdown) + }) + + g.Go(func() error { + <-ctx.Done() + svc.Logger.Info("Closing Ethereum client") + svc.ethClient.Close() + return nil + }) + + svc.setWorkerReady() + svc.Logger.Info("Worker started") + + return g.Wait() +} + +func (svc *Service) processWithdrawal(ctx context.Context, event *custody.WithdrawStartedEvent) { + wID := common.Hash(event.WithdrawalID).Hex() + logger := svc.Logger.With( + "withdrawal_id", wID, + "user", event.User.Hex(), + "token", event.Token.Hex(), + "amount", event.Amount, + ) + + if svc.store.HasWithdrawEvent(wID) { + logger.Info("Event already processed, skipping") + return + } + + logger.Info("Processing withdrawal request") + + baseModel := store.WithdrawEventModel{ + WithdrawalID: wID, + UserAddress: event.User.Hex(), + TokenAddress: event.Token.Hex(), + Amount: event.Amount.String(), + BlockNumber: event.BlockNumber, + TxHash: event.TxHash.Hex(), + LogIndex: uint(event.LogIndex), + } + + if err := svc.checker.Check(event.User, event.Token, event.Amount); err != nil { + logger.Warn("Withdrawal blocked by policy, rejecting", "reason", err) + + txAuth := *svc.auth + txAuth.Context = ctx + tx, txErr := svc.contract.RejectWithdraw(&txAuth, event.WithdrawalID) + if txErr != nil { + logger.Error("Failed to reject withdrawal", "error", txErr) + baseModel.Decision = "error" + baseModel.Reason = fmt.Sprintf("reject tx failed: %v", txErr) + svc.recordEvent(logger, &baseModel) + return + } + logger.Info("Sent reject transaction", "tx_hash", tx.Hash().Hex()) + if _, txErr = bind.WaitMined(ctx, svc.ethClient, tx); txErr != nil { + logger.Error("Failed waiting for reject tx to be mined", "error", txErr) + baseModel.Decision = "error" + baseModel.Reason = fmt.Sprintf("reject tx mining failed: %v", txErr) + svc.recordEvent(logger, &baseModel) + return + } + + baseModel.Decision = "rejected" + baseModel.Reason = err.Error() + svc.recordEvent(logger, &baseModel) + return + } + + txAuth := *svc.auth + txAuth.Context = ctx + + tx, err := svc.contract.FinalizeWithdraw(&txAuth, event.WithdrawalID) + if err != nil { + logger.Error("Failed to finalize withdrawal", "error", err) + baseModel.Decision = "error" + baseModel.Reason = fmt.Sprintf("finalize tx failed: %v", err) + svc.recordEvent(logger, &baseModel) + return + } + + logger.Info("Sent finalize transaction", "tx_hash", tx.Hash().Hex()) + + receipt, err := bind.WaitMined(ctx, svc.ethClient, tx) + if err != nil { + logger.Error("Transaction mining failed", "error", err) + baseModel.Decision = "error" + baseModel.Reason = fmt.Sprintf("finalize tx mining failed: %v", err) + svc.recordEvent(logger, &baseModel) + return + } + + if receipt.Status == 1 { + logger.Info("Withdrawal finalized successfully on-chain") + + record := &custody.Withdrawal{ + WithdrawalID: event.WithdrawalID, + User: event.User, + Token: event.Token, + Amount: event.Amount, + BlockNumber: receipt.BlockNumber.Uint64(), + TxHash: tx.Hash(), + Timestamp: time.Now(), + } + if err := svc.checker.Record(record); err != nil { + logger.Error("Failed to record withdrawal in DB", "error", err) + } + + baseModel.Decision = "approved" + svc.recordEvent(logger, &baseModel) + } else { + logger.Error("Withdrawal finalization tx reverted") + baseModel.Decision = "error" + baseModel.Reason = "finalize tx reverted on-chain" + svc.recordEvent(logger, &baseModel) + } +} + +func (svc *Service) recordEvent(logger *slog.Logger, ev *store.WithdrawEventModel) { + if err := svc.store.RecordWithdrawEvent(ev); err != nil { + logger.Error("Failed to record withdraw event", "error", err) + } +} + +func parseLimitsConfig(lc config.LimitsConfig) (map[common.Address]checker.Limit, error) { + limits := make(map[common.Address]checker.Limit) + for addrStr, conf := range lc { + if !common.IsHexAddress(addrStr) { + return nil, fmt.Errorf("invalid address: %s", addrStr) + } + addr := common.HexToAddress(addrStr) + + l := checker.Limit{} + if conf.Hourly != "" { + val, ok := new(big.Int).SetString(conf.Hourly, 10) + if !ok { + return nil, fmt.Errorf("invalid hourly limit for %s: %s", addrStr, conf.Hourly) + } + l.Hourly = val + } + if conf.Daily != "" { + val, ok := new(big.Int).SetString(conf.Daily, 10) + if !ok { + return nil, fmt.Errorf("invalid daily limit for %s: %s", addrStr, conf.Daily) + } + l.Daily = val + } + limits[addr] = l + } + return limits, nil +} + +func parseUserOverrides(overrides map[string]config.LimitsConfig) (map[common.Address]map[common.Address]checker.Limit, error) { + result := make(map[common.Address]map[common.Address]checker.Limit) + for userAddrStr, tokenLimits := range overrides { + if !common.IsHexAddress(userAddrStr) { + return nil, fmt.Errorf("invalid user address in per_user_overrides: %s", userAddrStr) + } + userAddr := common.HexToAddress(userAddrStr) + parsed, err := parseLimitsConfig(tokenLimits) + if err != nil { + return nil, fmt.Errorf("per-user overrides for %s: %w", userAddrStr, err) + } + result[userAddr] = parsed + } + return result, nil +} diff --git a/service/web_handlers.go b/service/web_handlers.go new file mode 100644 index 0000000..12b87ae --- /dev/null +++ b/service/web_handlers.go @@ -0,0 +1,92 @@ +package service + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +type healthStatus string + +const ( + statusHealthy healthStatus = "healthy" + statusDegraded healthStatus = "degraded" +) + +type healthResponse struct { + Status healthStatus `json:"status"` + Service string `json:"service"` + Timestamp string `json:"timestamp"` + Dependencies map[string]depCheck `json:"dependencies,omitempty"` +} + +type depCheck struct { + Status healthStatus `json:"status"` + Error string `json:"error,omitempty"` +} + +func newHealthResponse() healthResponse { + return healthResponse{ + Status: statusHealthy, + Service: "nitewatch", + Timestamp: time.Now().UTC().Format(time.RFC3339), + Dependencies: make(map[string]depCheck), + } +} + +func (r *healthResponse) addDependency(name string, check depCheck) { + r.Dependencies[name] = check + if check.Status == statusDegraded && r.Status == statusHealthy { + r.Status = statusDegraded + } +} + +func (r *healthResponse) httpStatusCode() int { + return http.StatusOK +} + +func attachWebHandlers(svc *Service) { + svc.web.Engine.GET("/health", getHealth(svc)) + svc.web.Engine.GET("/health/live", livenessHandler()) + svc.web.Engine.GET("/health/ready", readinessHandler(svc)) +} + +func getHealth(svc *Service) gin.HandlerFunc { + return func(c *gin.Context) { + status := "healthy" + if !svc.IsWorkerReady() { + status = "degraded" + } + c.JSON(http.StatusOK, gin.H{ + "status": status, + "worker": svc.IsWorkerReady(), + }) + } +} + +func livenessHandler() gin.HandlerFunc { + return func(c *gin.Context) { + response := newHealthResponse() + c.JSON(http.StatusOK, response) + } +} + +func readinessHandler(svc *Service) gin.HandlerFunc { + return func(c *gin.Context) { + response := newHealthResponse() + + if svc.IsWorkerReady() { + response.addDependency("worker", depCheck{ + Status: statusHealthy, + }) + } else { + response.addDependency("worker", depCheck{ + Status: statusDegraded, + Error: "worker not ready", + }) + } + + c.JSON(response.httpStatusCode(), response) + } +} diff --git a/store/adapter.go b/store/adapter.go deleted file mode 100644 index 9aa01b3..0000000 --- a/store/adapter.go +++ /dev/null @@ -1,72 +0,0 @@ -package store - -import ( - "fmt" - "math/big" - "time" - - "github.com/ethereum/go-ethereum/common" - "gorm.io/gorm" - - nw "github.com/layer-3/nitewatch" -) - -// WithdrawalModel is the GORM model for persisting withdrawal events. -type WithdrawalModel struct { - gorm.Model - WithdrawalID string `gorm:"uniqueIndex;type:varchar(66)"` // 0x + 64 hex chars - User string `gorm:"index;type:varchar(42)"` // 0x + 40 hex chars - Token string `gorm:"index;type:varchar(42)"` // 0x + 40 hex chars - Amount string `gorm:"type:text"` // big.Int as string - BlockNumber uint64 - TxHash string `gorm:"type:varchar(66)"` - Timestamp time.Time `gorm:"index"` -} - -// Adapter implements the WithdrawalStore interface using GORM. -type Adapter struct { - db *gorm.DB -} - -// NewAdapter initializes a new GORM adapter and runs migrations. -func NewAdapter(db *gorm.DB) (*Adapter, error) { - if err := db.AutoMigrate(&WithdrawalModel{}); err != nil { - return nil, err - } - return &Adapter{db: db}, nil -} - -// Save persists a withdrawal event to the database. -func (a *Adapter) Save(w *nw.Withdrawal) error { - model := &WithdrawalModel{ - WithdrawalID: common.Hash(w.WithdrawalID).Hex(), - User: w.User.Hex(), - Token: w.Token.Hex(), - Amount: w.Amount.String(), - BlockNumber: w.BlockNumber, - TxHash: w.TxHash.Hex(), - Timestamp: w.Timestamp, - } - return a.db.Create(model).Error -} - -// GetTotalWithdrawn calculates the total amount withdrawn for a token since a given time. -// Summation is performed in Go to preserve big.Int precision (SQLite stores amounts as strings). -func (a *Adapter) GetTotalWithdrawn(token common.Address, since time.Time) (*big.Int, error) { - var withdrawals []WithdrawalModel - - if err := a.db.Where("token = ? AND timestamp >= ?", token.Hex(), since).Find(&withdrawals).Error; err != nil { - return nil, err - } - - total := new(big.Int) - for _, w := range withdrawals { - amount, ok := new(big.Int).SetString(w.Amount, 10) - if !ok { - return nil, fmt.Errorf("corrupted amount in withdrawal %s: %q", w.WithdrawalID, w.Amount) - } - total.Add(total, amount) - } - - return total, nil -} From 4a4af3042b098c81f4e233ea813aa97940823b41 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Wed, 18 Feb 2026 15:42:42 +0100 Subject: [PATCH 2/9] fix: rename deployment dir to remove colon from path --- .../2026-02-17T102625.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename contracts/evm/deployments/11155111/{SimpleCustody.sol:SimpleCustody/2026-02-17T10:26:25.json => SimpleCustody.sol/2026-02-17T102625.json} (100%) diff --git a/contracts/evm/deployments/11155111/SimpleCustody.sol:SimpleCustody/2026-02-17T10:26:25.json b/contracts/evm/deployments/11155111/SimpleCustody.sol/2026-02-17T102625.json similarity index 100% rename from contracts/evm/deployments/11155111/SimpleCustody.sol:SimpleCustody/2026-02-17T10:26:25.json rename to contracts/evm/deployments/11155111/SimpleCustody.sol/2026-02-17T102625.json From 508ec41398376c13530468136d8318984e66bcb8 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 13:48:21 +0100 Subject: [PATCH 3/9] style: run `go fmt` --- internal/store/adapter.go | 3 ++- service/service.go | 2 +- service/web_handlers.go | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/store/adapter.go b/internal/store/adapter.go index 4cc6197..554de87 100644 --- a/internal/store/adapter.go +++ b/internal/store/adapter.go @@ -1,6 +1,7 @@ package store import ( + "errors" "fmt" "math/big" "time" @@ -102,7 +103,7 @@ func (a *Adapter) GetCursor(streamName string) (blockNumber uint64, logIndex uin var cursor BlockCursorModel result := a.db.Where("stream_name = ?", streamName).First(&cursor) if result.Error != nil { - if result.Error == gorm.ErrRecordNotFound { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { return 0, 0, nil } return 0, 0, result.Error diff --git a/service/service.go b/service/service.go index a0da734..3636a1b 100644 --- a/service/service.go +++ b/service/service.go @@ -40,7 +40,7 @@ func newHTTPServer(addr string) *httpServer { } } -func (s *httpServer) Run() error { return s.server.ListenAndServe() } +func (s *httpServer) Run() error { return s.server.ListenAndServe() } func (s *httpServer) Shutdown(ctx context.Context) error { return s.server.Shutdown(ctx) } type Service struct { diff --git a/service/web_handlers.go b/service/web_handlers.go index 12b87ae..dbc1309 100644 --- a/service/web_handlers.go +++ b/service/web_handlers.go @@ -15,10 +15,10 @@ const ( ) type healthResponse struct { - Status healthStatus `json:"status"` - Service string `json:"service"` - Timestamp string `json:"timestamp"` - Dependencies map[string]depCheck `json:"dependencies,omitempty"` + Status healthStatus `json:"status"` + Service string `json:"service"` + Timestamp string `json:"timestamp"` + Dependencies map[string]depCheck `json:"dependencies,omitempty"` } type depCheck struct { From 301fd3c7da3088db2ef51b012a77927582c97b39 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 13:48:47 +0100 Subject: [PATCH 4/9] chore: drop test ERC20 contract --- Makefile | 4 - contracts/evm/src/MockERC20.sol | 12 - custody/mock_erc20.go | 781 -------------------------------- 3 files changed, 797 deletions(-) delete mode 100644 contracts/evm/src/MockERC20.sol delete mode 100644 custody/mock_erc20.go diff --git a/Makefile b/Makefile index 7c7fcd0..09378a7 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,3 @@ custody/simple_custody.go: contracts/evm/out/.build-sentinel jq -r .bytecode.object contracts/evm/out/SimpleCustody.sol/SimpleCustody.json > custody/SimpleCustody.bin abigen --abi custody/SimpleCustody.abi --bin custody/SimpleCustody.bin --pkg custody --type SimpleCustody --out $@ -custody/mock_erc20.go: contracts/evm/out/.build-sentinel - jq .abi contracts/evm/out/MockERC20.sol/MockERC20.json > custody/MockERC20.abi - jq -r .bytecode.object contracts/evm/out/MockERC20.sol/MockERC20.json > custody/MockERC20.bin - abigen --abi custody/MockERC20.abi --bin custody/MockERC20.bin --pkg custody --type MockERC20 --out $@ diff --git a/contracts/evm/src/MockERC20.sol b/contracts/evm/src/MockERC20.sol deleted file mode 100644 index 02bfe9f..0000000 --- a/contracts/evm/src/MockERC20.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -contract MockERC20 is ERC20 { - constructor() ERC20("Mock", "MCK") {} - - function mint(address to, uint256 amount) external { - _mint(to, amount); - } -} diff --git a/custody/mock_erc20.go b/custody/mock_erc20.go deleted file mode 100644 index 7ee9ba8..0000000 --- a/custody/mock_erc20.go +++ /dev/null @@ -1,781 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package custody - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription - _ = abi.ConvertType -) - -// MockERC20MetaData contains all meta data concerning the MockERC20 contract. -var MockERC20MetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"allowance\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"approve\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"balanceOf\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"decimals\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint8\",\"internalType\":\"uint8\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"mint\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"name\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"symbol\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"totalSupply\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transfer\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferFrom\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Approval\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"spender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Transfer\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"value\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"ERC20InsufficientAllowance\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"allowance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"needed\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ERC20InsufficientBalance\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"balance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"needed\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidApprover\",\"inputs\":[{\"name\":\"approver\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidReceiver\",\"inputs\":[{\"name\":\"receiver\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidSender\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC20InvalidSpender\",\"inputs\":[{\"name\":\"spender\",\"type\":\"address\",\"internalType\":\"address\"}]}]", - Bin: "0x608060405234801561000f575f5ffd5b506040518060400160405280600481526020017f4d6f636b000000000000000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d434b0000000000000000000000000000000000000000000000000000000000815250816003908161008b91906102e0565b50806004908161009b91906102e0565b5050506103af565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f600282049050600182168061011e57607f821691505b602082108103610131576101306100da565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026101937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82610158565b61019d8683610158565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f6101e16101dc6101d7846101b5565b6101be565b6101b5565b9050919050565b5f819050919050565b6101fa836101c7565b61020e610206826101e8565b848454610164565b825550505050565b5f5f905090565b610225610216565b6102308184846101f1565b505050565b5b81811015610253576102485f8261021d565b600181019050610236565b5050565b601f8211156102985761026981610137565b61027284610149565b81016020851015610281578190505b61029561028d85610149565b830182610235565b50505b505050565b5f82821c905092915050565b5f6102b85f198460080261029d565b1980831691505092915050565b5f6102d083836102a9565b9150826002028217905092915050565b6102e9826100a3565b67ffffffffffffffff811115610302576103016100ad565b5b61030c8254610107565b610317828285610257565b5f60209050601f831160018114610348575f8415610336578287015190505b61034085826102c5565b8655506103a7565b601f19841661035686610137565b5f5b8281101561037d57848901518255600182019150602085019450602081019050610358565b8683101561039a5784890151610396601f8916826102a9565b8355505b6001600288020188555050505b505050505050565b610e96806103bc5f395ff3fe608060405234801561000f575f5ffd5b506004361061009c575f3560e01c806340c10f191161006457806340c10f191461015a57806370a082311461017657806395d89b41146101a6578063a9059cbb146101c4578063dd62ed3e146101f45761009c565b806306fdde03146100a0578063095ea7b3146100be57806318160ddd146100ee57806323b872dd1461010c578063313ce5671461013c575b5f5ffd5b6100a8610224565b6040516100b59190610b0f565b60405180910390f35b6100d860048036038101906100d39190610bc0565b6102b4565b6040516100e59190610c18565b60405180910390f35b6100f66102d6565b6040516101039190610c40565b60405180910390f35b61012660048036038101906101219190610c59565b6102df565b6040516101339190610c18565b60405180910390f35b61014461030d565b6040516101519190610cc4565b60405180910390f35b610174600480360381019061016f9190610bc0565b610315565b005b610190600480360381019061018b9190610cdd565b610323565b60405161019d9190610c40565b60405180910390f35b6101ae610368565b6040516101bb9190610b0f565b60405180910390f35b6101de60048036038101906101d99190610bc0565b6103f8565b6040516101eb9190610c18565b60405180910390f35b61020e60048036038101906102099190610d08565b61041a565b60405161021b9190610c40565b60405180910390f35b60606003805461023390610d73565b80601f016020809104026020016040519081016040528092919081815260200182805461025f90610d73565b80156102aa5780601f10610281576101008083540402835291602001916102aa565b820191905f5260205f20905b81548152906001019060200180831161028d57829003601f168201915b5050505050905090565b5f5f6102be61049c565b90506102cb8185856104a3565b600191505092915050565b5f600254905090565b5f5f6102e961049c565b90506102f68582856104b5565b610301858585610548565b60019150509392505050565b5f6012905090565b61031f8282610638565b5050565b5f5f5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b60606004805461037790610d73565b80601f01602080910402602001604051908101604052809291908181526020018280546103a390610d73565b80156103ee5780601f106103c5576101008083540402835291602001916103ee565b820191905f5260205f20905b8154815290600101906020018083116103d157829003601f168201915b5050505050905090565b5f5f61040261049c565b905061040f818585610548565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b6104b083838360016106b7565b505050565b5f6104c0848461041a565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8110156105425781811015610533578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161052a93929190610db2565b60405180910390fd5b61054184848484035f6106b7565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036105b8575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016105af9190610de7565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603610628575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161061f9190610de7565b60405180910390fd5b610633838383610886565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036106a8575f6040517fec442f0500000000000000000000000000000000000000000000000000000000815260040161069f9190610de7565b60405180910390fd5b6106b35f8383610886565b5050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff1603610727575f6040517fe602df0500000000000000000000000000000000000000000000000000000000815260040161071e9190610de7565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610797575f6040517f94280d6200000000000000000000000000000000000000000000000000000000815260040161078e9190610de7565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610880578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040516108779190610c40565b60405180910390a35b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036108d6578060025f8282546108ca9190610e2d565b925050819055506109a4565b5f5f5f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205490508181101561095f578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161095693929190610db2565b60405180910390fd5b8181035f5f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036109eb578060025f8282540392505081905550610a35565b805f5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051610a929190610c40565b60405180910390a3505050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f610ae182610a9f565b610aeb8185610aa9565b9350610afb818560208601610ab9565b610b0481610ac7565b840191505092915050565b5f6020820190508181035f830152610b278184610ad7565b905092915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610b5c82610b33565b9050919050565b610b6c81610b52565b8114610b76575f5ffd5b50565b5f81359050610b8781610b63565b92915050565b5f819050919050565b610b9f81610b8d565b8114610ba9575f5ffd5b50565b5f81359050610bba81610b96565b92915050565b5f5f60408385031215610bd657610bd5610b2f565b5b5f610be385828601610b79565b9250506020610bf485828601610bac565b9150509250929050565b5f8115159050919050565b610c1281610bfe565b82525050565b5f602082019050610c2b5f830184610c09565b92915050565b610c3a81610b8d565b82525050565b5f602082019050610c535f830184610c31565b92915050565b5f5f5f60608486031215610c7057610c6f610b2f565b5b5f610c7d86828701610b79565b9350506020610c8e86828701610b79565b9250506040610c9f86828701610bac565b9150509250925092565b5f60ff82169050919050565b610cbe81610ca9565b82525050565b5f602082019050610cd75f830184610cb5565b92915050565b5f60208284031215610cf257610cf1610b2f565b5b5f610cff84828501610b79565b91505092915050565b5f5f60408385031215610d1e57610d1d610b2f565b5b5f610d2b85828601610b79565b9250506020610d3c85828601610b79565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610d8a57607f821691505b602082108103610d9d57610d9c610d46565b5b50919050565b610dac81610b52565b82525050565b5f606082019050610dc55f830186610da3565b610dd26020830185610c31565b610ddf6040830184610c31565b949350505050565b5f602082019050610dfa5f830184610da3565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610e3782610b8d565b9150610e4283610b8d565b9250828201905080821115610e5a57610e59610e00565b5b9291505056fea264697066735822122094811535dfcfe0091b41027b6057239a0ab967fa4a0862a19f78329809ea0cbc64736f6c634300081e0033", -} - -// MockERC20ABI is the input ABI used to generate the binding from. -// Deprecated: Use MockERC20MetaData.ABI instead. -var MockERC20ABI = MockERC20MetaData.ABI - -// MockERC20Bin is the compiled bytecode used for deploying new contracts. -// Deprecated: Use MockERC20MetaData.Bin instead. -var MockERC20Bin = MockERC20MetaData.Bin - -// DeployMockERC20 deploys a new Ethereum contract, binding an instance of MockERC20 to it. -func DeployMockERC20(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *MockERC20, error) { - parsed, err := MockERC20MetaData.GetAbi() - if err != nil { - return common.Address{}, nil, nil, err - } - if parsed == nil { - return common.Address{}, nil, nil, errors.New("GetABI returned nil") - } - - address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(MockERC20Bin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &MockERC20{MockERC20Caller: MockERC20Caller{contract: contract}, MockERC20Transactor: MockERC20Transactor{contract: contract}, MockERC20Filterer: MockERC20Filterer{contract: contract}}, nil -} - -// MockERC20 is an auto generated Go binding around an Ethereum contract. -type MockERC20 struct { - MockERC20Caller // Read-only binding to the contract - MockERC20Transactor // Write-only binding to the contract - MockERC20Filterer // Log filterer for contract events -} - -// MockERC20Caller is an auto generated read-only Go binding around an Ethereum contract. -type MockERC20Caller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// MockERC20Transactor is an auto generated write-only Go binding around an Ethereum contract. -type MockERC20Transactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// MockERC20Filterer is an auto generated log filtering Go binding around an Ethereum contract events. -type MockERC20Filterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// MockERC20Session is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type MockERC20Session struct { - Contract *MockERC20 // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// MockERC20CallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type MockERC20CallerSession struct { - Contract *MockERC20Caller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// MockERC20TransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type MockERC20TransactorSession struct { - Contract *MockERC20Transactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// MockERC20Raw is an auto generated low-level Go binding around an Ethereum contract. -type MockERC20Raw struct { - Contract *MockERC20 // Generic contract binding to access the raw methods on -} - -// MockERC20CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type MockERC20CallerRaw struct { - Contract *MockERC20Caller // Generic read-only contract binding to access the raw methods on -} - -// MockERC20TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type MockERC20TransactorRaw struct { - Contract *MockERC20Transactor // Generic write-only contract binding to access the raw methods on -} - -// NewMockERC20 creates a new instance of MockERC20, bound to a specific deployed contract. -func NewMockERC20(address common.Address, backend bind.ContractBackend) (*MockERC20, error) { - contract, err := bindMockERC20(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &MockERC20{MockERC20Caller: MockERC20Caller{contract: contract}, MockERC20Transactor: MockERC20Transactor{contract: contract}, MockERC20Filterer: MockERC20Filterer{contract: contract}}, nil -} - -// NewMockERC20Caller creates a new read-only instance of MockERC20, bound to a specific deployed contract. -func NewMockERC20Caller(address common.Address, caller bind.ContractCaller) (*MockERC20Caller, error) { - contract, err := bindMockERC20(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &MockERC20Caller{contract: contract}, nil -} - -// NewMockERC20Transactor creates a new write-only instance of MockERC20, bound to a specific deployed contract. -func NewMockERC20Transactor(address common.Address, transactor bind.ContractTransactor) (*MockERC20Transactor, error) { - contract, err := bindMockERC20(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &MockERC20Transactor{contract: contract}, nil -} - -// NewMockERC20Filterer creates a new log filterer instance of MockERC20, bound to a specific deployed contract. -func NewMockERC20Filterer(address common.Address, filterer bind.ContractFilterer) (*MockERC20Filterer, error) { - contract, err := bindMockERC20(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &MockERC20Filterer{contract: contract}, nil -} - -// bindMockERC20 binds a generic wrapper to an already deployed contract. -func bindMockERC20(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := MockERC20MetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_MockERC20 *MockERC20Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _MockERC20.Contract.MockERC20Caller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_MockERC20 *MockERC20Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _MockERC20.Contract.MockERC20Transactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_MockERC20 *MockERC20Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _MockERC20.Contract.MockERC20Transactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_MockERC20 *MockERC20CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _MockERC20.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_MockERC20 *MockERC20TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _MockERC20.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_MockERC20 *MockERC20TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _MockERC20.Contract.contract.Transact(opts, method, params...) -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_MockERC20 *MockERC20Caller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { - var out []interface{} - err := _MockERC20.contract.Call(opts, &out, "allowance", owner, spender) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_MockERC20 *MockERC20Session) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { - return _MockERC20.Contract.Allowance(&_MockERC20.CallOpts, owner, spender) -} - -// Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. -// -// Solidity: function allowance(address owner, address spender) view returns(uint256) -func (_MockERC20 *MockERC20CallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { - return _MockERC20.Contract.Allowance(&_MockERC20.CallOpts, owner, spender) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address account) view returns(uint256) -func (_MockERC20 *MockERC20Caller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) { - var out []interface{} - err := _MockERC20.contract.Call(opts, &out, "balanceOf", account) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address account) view returns(uint256) -func (_MockERC20 *MockERC20Session) BalanceOf(account common.Address) (*big.Int, error) { - return _MockERC20.Contract.BalanceOf(&_MockERC20.CallOpts, account) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address account) view returns(uint256) -func (_MockERC20 *MockERC20CallerSession) BalanceOf(account common.Address) (*big.Int, error) { - return _MockERC20.Contract.BalanceOf(&_MockERC20.CallOpts, account) -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_MockERC20 *MockERC20Caller) Decimals(opts *bind.CallOpts) (uint8, error) { - var out []interface{} - err := _MockERC20.contract.Call(opts, &out, "decimals") - - if err != nil { - return *new(uint8), err - } - - out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) - - return out0, err - -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_MockERC20 *MockERC20Session) Decimals() (uint8, error) { - return _MockERC20.Contract.Decimals(&_MockERC20.CallOpts) -} - -// Decimals is a free data retrieval call binding the contract method 0x313ce567. -// -// Solidity: function decimals() view returns(uint8) -func (_MockERC20 *MockERC20CallerSession) Decimals() (uint8, error) { - return _MockERC20.Contract.Decimals(&_MockERC20.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_MockERC20 *MockERC20Caller) Name(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _MockERC20.contract.Call(opts, &out, "name") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_MockERC20 *MockERC20Session) Name() (string, error) { - return _MockERC20.Contract.Name(&_MockERC20.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_MockERC20 *MockERC20CallerSession) Name() (string, error) { - return _MockERC20.Contract.Name(&_MockERC20.CallOpts) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_MockERC20 *MockERC20Caller) Symbol(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _MockERC20.contract.Call(opts, &out, "symbol") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_MockERC20 *MockERC20Session) Symbol() (string, error) { - return _MockERC20.Contract.Symbol(&_MockERC20.CallOpts) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_MockERC20 *MockERC20CallerSession) Symbol() (string, error) { - return _MockERC20.Contract.Symbol(&_MockERC20.CallOpts) -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_MockERC20 *MockERC20Caller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _MockERC20.contract.Call(opts, &out, "totalSupply") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_MockERC20 *MockERC20Session) TotalSupply() (*big.Int, error) { - return _MockERC20.Contract.TotalSupply(&_MockERC20.CallOpts) -} - -// TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. -// -// Solidity: function totalSupply() view returns(uint256) -func (_MockERC20 *MockERC20CallerSession) TotalSupply() (*big.Int, error) { - return _MockERC20.Contract.TotalSupply(&_MockERC20.CallOpts) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address spender, uint256 value) returns(bool) -func (_MockERC20 *MockERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.contract.Transact(opts, "approve", spender, value) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address spender, uint256 value) returns(bool) -func (_MockERC20 *MockERC20Session) Approve(spender common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.Approve(&_MockERC20.TransactOpts, spender, value) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address spender, uint256 value) returns(bool) -func (_MockERC20 *MockERC20TransactorSession) Approve(spender common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.Approve(&_MockERC20.TransactOpts, spender, value) -} - -// Mint is a paid mutator transaction binding the contract method 0x40c10f19. -// -// Solidity: function mint(address to, uint256 amount) returns() -func (_MockERC20 *MockERC20Transactor) Mint(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { - return _MockERC20.contract.Transact(opts, "mint", to, amount) -} - -// Mint is a paid mutator transaction binding the contract method 0x40c10f19. -// -// Solidity: function mint(address to, uint256 amount) returns() -func (_MockERC20 *MockERC20Session) Mint(to common.Address, amount *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.Mint(&_MockERC20.TransactOpts, to, amount) -} - -// Mint is a paid mutator transaction binding the contract method 0x40c10f19. -// -// Solidity: function mint(address to, uint256 amount) returns() -func (_MockERC20 *MockERC20TransactorSession) Mint(to common.Address, amount *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.Mint(&_MockERC20.TransactOpts, to, amount) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address to, uint256 value) returns(bool) -func (_MockERC20 *MockERC20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.contract.Transact(opts, "transfer", to, value) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address to, uint256 value) returns(bool) -func (_MockERC20 *MockERC20Session) Transfer(to common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.Transfer(&_MockERC20.TransactOpts, to, value) -} - -// Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. -// -// Solidity: function transfer(address to, uint256 value) returns(bool) -func (_MockERC20 *MockERC20TransactorSession) Transfer(to common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.Transfer(&_MockERC20.TransactOpts, to, value) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) -func (_MockERC20 *MockERC20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.contract.Transact(opts, "transferFrom", from, to, value) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) -func (_MockERC20 *MockERC20Session) TransferFrom(from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.TransferFrom(&_MockERC20.TransactOpts, from, to, value) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) -func (_MockERC20 *MockERC20TransactorSession) TransferFrom(from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { - return _MockERC20.Contract.TransferFrom(&_MockERC20.TransactOpts, from, to, value) -} - -// MockERC20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the MockERC20 contract. -type MockERC20ApprovalIterator struct { - Event *MockERC20Approval // 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 *MockERC20ApprovalIterator) 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(MockERC20Approval) - 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(MockERC20Approval) - 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 *MockERC20ApprovalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *MockERC20ApprovalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// MockERC20Approval represents a Approval event raised by the MockERC20 contract. -type MockERC20Approval struct { - Owner common.Address - Spender common.Address - Value *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) -func (_MockERC20 *MockERC20Filterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*MockERC20ApprovalIterator, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var spenderRule []interface{} - for _, spenderItem := range spender { - spenderRule = append(spenderRule, spenderItem) - } - - logs, sub, err := _MockERC20.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) - if err != nil { - return nil, err - } - return &MockERC20ApprovalIterator{contract: _MockERC20.contract, event: "Approval", logs: logs, sub: sub}, nil -} - -// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) -func (_MockERC20 *MockERC20Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *MockERC20Approval, owner []common.Address, spender []common.Address) (event.Subscription, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var spenderRule []interface{} - for _, spenderItem := range spender { - spenderRule = append(spenderRule, spenderItem) - } - - logs, sub, err := _MockERC20.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) - 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(MockERC20Approval) - if err := _MockERC20.contract.UnpackLog(event, "Approval", 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 -} - -// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) -func (_MockERC20 *MockERC20Filterer) ParseApproval(log types.Log) (*MockERC20Approval, error) { - event := new(MockERC20Approval) - if err := _MockERC20.contract.UnpackLog(event, "Approval", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// MockERC20TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the MockERC20 contract. -type MockERC20TransferIterator struct { - Event *MockERC20Transfer // 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 *MockERC20TransferIterator) 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(MockERC20Transfer) - 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(MockERC20Transfer) - 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 *MockERC20TransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *MockERC20TransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// MockERC20Transfer represents a Transfer event raised by the MockERC20 contract. -type MockERC20Transfer struct { - From common.Address - To common.Address - Value *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) -func (_MockERC20 *MockERC20Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*MockERC20TransferIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _MockERC20.contract.FilterLogs(opts, "Transfer", fromRule, toRule) - if err != nil { - return nil, err - } - return &MockERC20TransferIterator{contract: _MockERC20.contract, event: "Transfer", logs: logs, sub: sub}, nil -} - -// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) -func (_MockERC20 *MockERC20Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *MockERC20Transfer, from []common.Address, to []common.Address) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - - logs, sub, err := _MockERC20.contract.WatchLogs(opts, "Transfer", fromRule, toRule) - 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(MockERC20Transfer) - if err := _MockERC20.contract.UnpackLog(event, "Transfer", 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 -} - -// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) -func (_MockERC20 *MockERC20Filterer) ParseTransfer(log types.Log) (*MockERC20Transfer, error) { - event := new(MockERC20Transfer) - if err := _MockERC20.contract.UnpackLog(event, "Transfer", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} From d3b77f16fae23ad5c5a75b2bc5ca77e14e6c8e22 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 14:08:45 +0100 Subject: [PATCH 5/9] refactor: redundant code cleanup --- Makefile | 2 +- custody/anviltest/anviltest.go | 146 --------------------------------- custody/ethlistener.go | 3 - service/service.go | 2 - service/web_handlers.go | 92 --------------------- 5 files changed, 1 insertion(+), 244 deletions(-) delete mode 100644 custody/anviltest/anviltest.go delete mode 100644 service/web_handlers.go diff --git a/Makefile b/Makefile index 09378a7..514ce0d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SOL_SOURCES := $(shell find contracts/evm/src -name '*.sol') -BINDINGS := custody/iwithdraw.go custody/ideposit.go custody/simple_custody.go custody/mock_erc20.go +BINDINGS := custody/iwithdraw.go custody/ideposit.go custody/simple_custody.go .PHONY: generate generate: $(BINDINGS) diff --git a/custody/anviltest/anviltest.go b/custody/anviltest/anviltest.go deleted file mode 100644 index a94c445..0000000 --- a/custody/anviltest/anviltest.go +++ /dev/null @@ -1,146 +0,0 @@ -package anviltest - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/ethclient" - "github.com/stretchr/testify/require" - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/wait" -) - -var ( - sharedContainer testcontainers.Container - sharedURLs URLs - sharedOnce sync.Once - sharedErr error -) - -// Anvil deterministic private keys (accounts pre-funded with 10000 ETH each). -const ( - Account0Key = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" - Account1Key = "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" - Account2Key = "5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a" -) - -// URLs holds both HTTP and WebSocket URLs for an Anvil instance. -// HTTP is used for one-shot RPC calls (deploy, transact), WS for event subscriptions. -type URLs struct { - HTTP string - WS string -} - -// Setup starts an Anvil testcontainer and returns HTTP and WebSocket URLs. -// The container is automatically terminated when the test finishes. -func Setup(t *testing.T) URLs { - t.Helper() - - ctx := t.Context() - - anvilContainer, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Image: "ghcr.io/foundry-rs/foundry:latest", - ExposedPorts: []string{"8545/tcp"}, - // Image ENTRYPOINT is ["/bin/sh", "-c"], so the command must be a single string. - Cmd: []string{"anvil --host 0.0.0.0"}, - WaitingFor: wait.ForAll( - wait.ForLog("Listening on"), - wait.ForListeningPort("8545/tcp"), - ), - }, - Started: true, - }) - require.NoError(t, err) - - t.Cleanup(func() { - if err := anvilContainer.Terminate(context.Background()); err != nil { - t.Log("Anvil container terminated with error:", err) - } - }) - - host, err := anvilContainer.Host(ctx) - require.NoError(t, err) - - port, err := anvilContainer.MappedPort(ctx, "8545") - require.NoError(t, err) - - return URLs{ - HTTP: fmt.Sprintf("http://%s:%s", host, port.Port()), - WS: fmt.Sprintf("ws://%s:%s", host, port.Port()), - } -} - -// SetupShared starts a shared Anvil container (once) and returns its URLs. -// Call TerminateShared in TestMain to clean up after all tests. -func SetupShared(ctx context.Context) (URLs, error) { - sharedOnce.Do(func() { - var container testcontainers.Container - container, sharedErr = testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ - ContainerRequest: testcontainers.ContainerRequest{ - Image: "ghcr.io/foundry-rs/foundry:latest", - ExposedPorts: []string{"8545/tcp"}, - Cmd: []string{"anvil --host 0.0.0.0"}, - WaitingFor: wait.ForAll( - wait.ForLog("Listening on"), - wait.ForListeningPort("8545/tcp"), - ), - }, - Started: true, - }) - if sharedErr != nil { - return - } - sharedContainer = container - - host, err := container.Host(ctx) - if err != nil { - sharedErr = err - return - } - port, err := container.MappedPort(ctx, "8545") - if err != nil { - sharedErr = err - return - } - sharedURLs = URLs{ - HTTP: fmt.Sprintf("http://%s:%s", host, port.Port()), - WS: fmt.Sprintf("ws://%s:%s", host, port.Port()), - } - }) - return sharedURLs, sharedErr -} - -// TerminateShared stops the shared Anvil container. Call from TestMain. -func TerminateShared(ctx context.Context) error { - if sharedContainer != nil { - return sharedContainer.Terminate(ctx) - } - return nil -} - -// DialHTTP connects to an Anvil node via HTTP with retry for container readiness. -// Retries up to 10 times with 500ms backoff, verifying with a ChainID() call. -func DialHTTP(t *testing.T, httpURL string) *ethclient.Client { - t.Helper() - - var client *ethclient.Client - var err error - for i := 0; i < 10; i++ { - client, err = ethclient.Dial(httpURL) - if err == nil { - _, chainErr := client.ChainID(context.Background()) - if chainErr == nil { - return client - } - client.Close() - err = chainErr - } - time.Sleep(500 * time.Millisecond) - } - require.NoError(t, err, "failed to connect to Anvil after retries") - return nil -} diff --git a/custody/ethlistener.go b/custody/ethlistener.go index f2e5bfa..735cda4 100644 --- a/custody/ethlistener.go +++ b/custody/ethlistener.go @@ -1,6 +1,3 @@ -// Copied from github.com/layer-3/pathfinder/pkg/ethlistener (commit hash 686dc94b80985eba798fdec499b9a802dbf80471). -// Adapted: replaced slog with ipfs/go-log, removed FetchHistoricalLogs, -// which depends on pathfinder's ethclient package. package custody import ( diff --git a/service/service.go b/service/service.go index 3636a1b..918c7c5 100644 --- a/service/service.go +++ b/service/service.go @@ -146,8 +146,6 @@ func (svc *Service) RunWorkerWithContext(ctx context.Context) error { ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) defer cancel() - attachWebHandlers(svc) - g, ctx := errgroup.WithContext(ctx) g.Go(func() error { diff --git a/service/web_handlers.go b/service/web_handlers.go deleted file mode 100644 index dbc1309..0000000 --- a/service/web_handlers.go +++ /dev/null @@ -1,92 +0,0 @@ -package service - -import ( - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -type healthStatus string - -const ( - statusHealthy healthStatus = "healthy" - statusDegraded healthStatus = "degraded" -) - -type healthResponse struct { - Status healthStatus `json:"status"` - Service string `json:"service"` - Timestamp string `json:"timestamp"` - Dependencies map[string]depCheck `json:"dependencies,omitempty"` -} - -type depCheck struct { - Status healthStatus `json:"status"` - Error string `json:"error,omitempty"` -} - -func newHealthResponse() healthResponse { - return healthResponse{ - Status: statusHealthy, - Service: "nitewatch", - Timestamp: time.Now().UTC().Format(time.RFC3339), - Dependencies: make(map[string]depCheck), - } -} - -func (r *healthResponse) addDependency(name string, check depCheck) { - r.Dependencies[name] = check - if check.Status == statusDegraded && r.Status == statusHealthy { - r.Status = statusDegraded - } -} - -func (r *healthResponse) httpStatusCode() int { - return http.StatusOK -} - -func attachWebHandlers(svc *Service) { - svc.web.Engine.GET("/health", getHealth(svc)) - svc.web.Engine.GET("/health/live", livenessHandler()) - svc.web.Engine.GET("/health/ready", readinessHandler(svc)) -} - -func getHealth(svc *Service) gin.HandlerFunc { - return func(c *gin.Context) { - status := "healthy" - if !svc.IsWorkerReady() { - status = "degraded" - } - c.JSON(http.StatusOK, gin.H{ - "status": status, - "worker": svc.IsWorkerReady(), - }) - } -} - -func livenessHandler() gin.HandlerFunc { - return func(c *gin.Context) { - response := newHealthResponse() - c.JSON(http.StatusOK, response) - } -} - -func readinessHandler(svc *Service) gin.HandlerFunc { - return func(c *gin.Context) { - response := newHealthResponse() - - if svc.IsWorkerReady() { - response.addDependency("worker", depCheck{ - Status: statusHealthy, - }) - } else { - response.addDependency("worker", depCheck{ - Status: statusDegraded, - Error: "worker not ready", - }) - } - - c.JSON(response.httpStatusCode(), response) - } -} From d130587d7729bdd571f8cdcf24b527384af1894e Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 14:23:38 +0100 Subject: [PATCH 6/9] refactor: merge ethlistener.go into listener.go --- custody/ethlistener.go | 260 ----------------------------------------- custody/listener.go | 253 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 253 insertions(+), 260 deletions(-) delete mode 100644 custody/ethlistener.go diff --git a/custody/ethlistener.go b/custody/ethlistener.go deleted file mode 100644 index 735cda4..0000000 --- a/custody/ethlistener.go +++ /dev/null @@ -1,260 +0,0 @@ -package custody - -import ( - "context" - "errors" - "fmt" - "math/big" - "regexp" - "strconv" - "strings" - "sync/atomic" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" - logging "github.com/ipfs/go-log/v2" - "github.com/layer-3/clearsync/pkg/debounce" -) - -var ethLogger = logging.Logger("ethlistener") - -const ( - maxBackOffCount = 5 -) - -type logHandler func(log types.Log) - -func listenEvents( - ctx context.Context, - client bind.ContractBackend, - subID string, - contractAddress common.Address, - networkID uint32, - lastBlock uint64, - lastIndex uint32, - topics [][]common.Hash, - handler logHandler, -) { - var backOffCount atomic.Uint64 - var historicalCh, currentCh chan types.Log - var eventSubscription event.Subscription - - ethLogger.Debugw("starting listening events", "subID", subID, "contractAddress", contractAddress.String()) - for { - if err := ctx.Err(); err != nil { - ethLogger.Infow("context cancelled, stopping listener", "subID", subID) - if eventSubscription != nil { - eventSubscription.Unsubscribe() - } - return - } - - if eventSubscription == nil { - if !waitForBackOffTimeout(ctx, int(backOffCount.Load()), "event subscription") { - return - } - - historicalCh = make(chan types.Log, 1) - currentCh = make(chan types.Log, 100) - - if lastBlock == 0 { - ethLogger.Infow("skipping historical logs fetching", "subID", subID, "contractAddress", contractAddress.String()) - } else { - var header *types.Header - var err error - headerCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) - err = debounce.Debounce(headerCtx, ethLogger, func(ctx context.Context) error { - header, err = client.HeaderByNumber(ctx, nil) - return err - }) - cancel() - if err != nil { - if ctx.Err() != nil { - return - } - ethLogger.Errorw("failed to get latest block", "error", err, "subID", subID, "contractAddress", contractAddress.String()) - backOffCount.Add(1) - continue - } - - go reconcileBlockRange( - ctx, - client, - subID, - contractAddress, - networkID, - header.Number.Uint64(), - lastBlock, - lastIndex, - topics, - historicalCh, - ) - } - - watchFQ := ethereum.FilterQuery{ - Addresses: []common.Address{contractAddress}, - } - eventSub, err := client.SubscribeFilterLogs(ctx, watchFQ, currentCh) - if err != nil { - if ctx.Err() != nil { - return - } - ethLogger.Errorw("failed to subscribe on events", "error", err, "subID", subID, "contractAddress", contractAddress.String()) - backOffCount.Add(1) - continue - } - - eventSubscription = eventSub - ethLogger.Infow("watching events", "subID", subID, "contractAddress", contractAddress.String()) - backOffCount.Store(0) - } - - select { - case <-ctx.Done(): - ethLogger.Infow("context cancelled, stopping listener", "subID", subID) - eventSubscription.Unsubscribe() - return - case eventLog := <-historicalCh: - ethLogger.Debugw("received historical event", "subID", subID, "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) - handler(eventLog) - case eventLog := <-currentCh: - lastBlock = eventLog.BlockNumber - ethLogger.Debugw("received new event", "subID", subID, "blockNumber", lastBlock, "logIndex", eventLog.Index) - handler(eventLog) - case err := <-eventSubscription.Err(): - if err != nil { - ethLogger.Errorw("event subscription error", "error", err, "subID", subID, "contractAddress", contractAddress.String()) - eventSubscription.Unsubscribe() - } else { - ethLogger.Debugw("subscription closed, resubscribing", "subID", subID, "contractAddress", contractAddress.String()) - } - - eventSubscription = nil - } - } -} - -func reconcileBlockRange( - ctx context.Context, - client bind.ContractBackend, - subID string, - contractAddress common.Address, - networkID uint32, - currentBlock uint64, - lastBlock uint64, - lastIndex uint32, - topics [][]common.Hash, - historicalCh chan types.Log, -) { - var backOffCount atomic.Uint64 - const blockStep = 10000 - startBlock := lastBlock - endBlock := startBlock + blockStep - - for currentBlock > startBlock { - if ctx.Err() != nil { - return - } - if !waitForBackOffTimeout(ctx, int(backOffCount.Load()), "reconcile block range") { - return - } - - if endBlock > currentBlock { - endBlock = currentBlock - } - - fetchFQ := ethereum.FilterQuery{ - Addresses: []common.Address{contractAddress}, - FromBlock: new(big.Int).SetUint64(startBlock), - ToBlock: new(big.Int).SetUint64(endBlock), - Topics: topics, - } - - var logs []types.Log - var err error - logsCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) - err = debounce.Debounce(logsCtx, ethLogger, func(ctx context.Context) error { - logs, err = client.FilterLogs(ctx, fetchFQ) - return err - }) - cancel() - if err != nil { - if strings.Contains(err.Error(), "Exceeded max range limit for eth_getLogs:") { - newEndBlock := endBlock - (endBlock-startBlock)/2 - ethLogger.Infow("eth_getLogs exceeded max range limit, reducing block range", "subID", subID, "startBlock", startBlock, "oldEndBlock", endBlock, "newEndBlock", newEndBlock) - endBlock = newEndBlock - continue - } - - newStartBlock, newEndBlock, extractErr := extractAdvisedBlockRange(err.Error()) - if extractErr != nil { - ethLogger.Errorw("failed to filter logs", "error", err, "extractErr", extractErr, "subID", subID, "startBlock", startBlock, "endBlock", endBlock) - backOffCount.Add(1) - continue - } - startBlock, endBlock = newStartBlock, newEndBlock - ethLogger.Infow("retrying with advised block range", "subID", subID, "startBlock", startBlock, "endBlock", endBlock) - continue - } - ethLogger.Infow("fetched historical logs", "subID", subID, "count", len(logs), "startBlock", startBlock, "endBlock", endBlock) - - for _, ethLog := range logs { - if ethLog.BlockNumber == lastBlock && ethLog.Index <= uint(lastIndex) { - ethLogger.Infow("skipping previously known event", "subID", subID, "blockNumber", ethLog.BlockNumber, "logIndex", ethLog.Index) - continue - } - - historicalCh <- ethLog - } - - startBlock = endBlock + 1 - endBlock += blockStep - } -} - -func extractAdvisedBlockRange(msg string) (startBlock, endBlock uint64, err error) { - if !strings.Contains(msg, "query returned more than 10000 results") { - err = errors.New("error message doesn't contain advised block range") - return - } - - re := regexp.MustCompile(`\[0x([0-9a-fA-F]+), 0x([0-9a-fA-F]+)\]`) - match := re.FindStringSubmatch(msg) - if len(match) != 3 { - err = errors.New("failed to extract block range from error message") - return - } - - startBlock, err = strconv.ParseUint(match[1], 16, 64) - if err != nil { - err = fmt.Errorf("failed to parse block range from error message: %w", err) - return - } - endBlock, err = strconv.ParseUint(match[2], 16, 64) - if err != nil { - err = fmt.Errorf("failed to parse block range from error message: %w", err) - return - } - return -} - -func waitForBackOffTimeout(ctx context.Context, backOffCount int, originator string) bool { - if backOffCount > maxBackOffCount { - ethLogger.Errorw("back off limit reached, exiting", "originator", originator, "backOffCount", backOffCount) - return true - } - - if backOffCount > 0 { - ethLogger.Infow("backing off", "originator", originator, "backOffCount", backOffCount) - select { - case <-time.After(time.Duration(2^backOffCount-1) * time.Second): - case <-ctx.Done(): - return false - } - } - return true -} diff --git a/custody/listener.go b/custody/listener.go index 3629511..280aa4a 100644 --- a/custody/listener.go +++ b/custody/listener.go @@ -2,10 +2,28 @@ package custody import ( "context" + "errors" + "fmt" + "math/big" + "regexp" + "strconv" + "strings" + "sync/atomic" + "time" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" + logging "github.com/ipfs/go-log/v2" + "github.com/layer-3/clearsync/pkg/debounce" +) + +var listenerLogger = logging.Logger("custody-listener") + +const ( + maxBackOffCount = 5 ) // Listener handles monitoring the blockchain for events from the custody contract. @@ -132,3 +150,238 @@ func (l *Listener) WatchDeposited(ctx context.Context, sink chan<- *DepositedEve }, ) } + +// listenEvents subscribes to on-chain logs matching the given topics and feeds them to handler. +// It handles reconnection with backoff, historical log reconciliation, and live subscription. +type logHandler func(log types.Log) + +func listenEvents( + ctx context.Context, + client bind.ContractBackend, + subID string, + contractAddress common.Address, + networkID uint32, + lastBlock uint64, + lastIndex uint32, + topics [][]common.Hash, + handler logHandler, +) { + var backOffCount atomic.Uint64 + var historicalCh, currentCh chan types.Log + var eventSubscription event.Subscription + + listenerLogger.Debugw("starting listening events", "subID", subID, "contractAddress", contractAddress.String()) + for { + if err := ctx.Err(); err != nil { + listenerLogger.Infow("context cancelled, stopping listener", "subID", subID) + if eventSubscription != nil { + eventSubscription.Unsubscribe() + } + return + } + + if eventSubscription == nil { + if !waitForBackOffTimeout(ctx, int(backOffCount.Load()), "event subscription") { + return + } + + historicalCh = make(chan types.Log, 1) + currentCh = make(chan types.Log, 100) + + if lastBlock == 0 { + listenerLogger.Infow("skipping historical logs fetching", "subID", subID, "contractAddress", contractAddress.String()) + } else { + var header *types.Header + var err error + headerCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) + err = debounce.Debounce(headerCtx, listenerLogger, func(ctx context.Context) error { + header, err = client.HeaderByNumber(ctx, nil) + return err + }) + cancel() + if err != nil { + if ctx.Err() != nil { + return + } + listenerLogger.Errorw("failed to get latest block", "error", err, "subID", subID, "contractAddress", contractAddress.String()) + backOffCount.Add(1) + continue + } + + go reconcileBlockRange( + ctx, + client, + subID, + contractAddress, + networkID, + header.Number.Uint64(), + lastBlock, + lastIndex, + topics, + historicalCh, + ) + } + + watchFQ := ethereum.FilterQuery{ + Addresses: []common.Address{contractAddress}, + } + eventSub, err := client.SubscribeFilterLogs(ctx, watchFQ, currentCh) + if err != nil { + if ctx.Err() != nil { + return + } + listenerLogger.Errorw("failed to subscribe on events", "error", err, "subID", subID, "contractAddress", contractAddress.String()) + backOffCount.Add(1) + continue + } + + eventSubscription = eventSub + listenerLogger.Infow("watching events", "subID", subID, "contractAddress", contractAddress.String()) + backOffCount.Store(0) + } + + select { + case <-ctx.Done(): + listenerLogger.Infow("context cancelled, stopping listener", "subID", subID) + eventSubscription.Unsubscribe() + return + case eventLog := <-historicalCh: + listenerLogger.Debugw("received historical event", "subID", subID, "blockNumber", eventLog.BlockNumber, "logIndex", eventLog.Index) + handler(eventLog) + case eventLog := <-currentCh: + lastBlock = eventLog.BlockNumber + listenerLogger.Debugw("received new event", "subID", subID, "blockNumber", lastBlock, "logIndex", eventLog.Index) + handler(eventLog) + case err := <-eventSubscription.Err(): + if err != nil { + listenerLogger.Errorw("event subscription error", "error", err, "subID", subID, "contractAddress", contractAddress.String()) + eventSubscription.Unsubscribe() + } else { + listenerLogger.Debugw("subscription closed, resubscribing", "subID", subID, "contractAddress", contractAddress.String()) + } + + eventSubscription = nil + } + } +} + +func reconcileBlockRange( + ctx context.Context, + client bind.ContractBackend, + subID string, + contractAddress common.Address, + networkID uint32, + currentBlock uint64, + lastBlock uint64, + lastIndex uint32, + topics [][]common.Hash, + historicalCh chan types.Log, +) { + var backOffCount atomic.Uint64 + const blockStep = 10000 + startBlock := lastBlock + endBlock := startBlock + blockStep + + for currentBlock > startBlock { + if ctx.Err() != nil { + return + } + if !waitForBackOffTimeout(ctx, int(backOffCount.Load()), "reconcile block range") { + return + } + + if endBlock > currentBlock { + endBlock = currentBlock + } + + fetchFQ := ethereum.FilterQuery{ + Addresses: []common.Address{contractAddress}, + FromBlock: new(big.Int).SetUint64(startBlock), + ToBlock: new(big.Int).SetUint64(endBlock), + Topics: topics, + } + + var logs []types.Log + var err error + logsCtx, cancel := context.WithTimeout(ctx, 1*time.Minute) + err = debounce.Debounce(logsCtx, listenerLogger, func(ctx context.Context) error { + logs, err = client.FilterLogs(ctx, fetchFQ) + return err + }) + cancel() + if err != nil { + if strings.Contains(err.Error(), "Exceeded max range limit for eth_getLogs:") { + newEndBlock := endBlock - (endBlock-startBlock)/2 + listenerLogger.Infow("eth_getLogs exceeded max range limit, reducing block range", "subID", subID, "startBlock", startBlock, "oldEndBlock", endBlock, "newEndBlock", newEndBlock) + endBlock = newEndBlock + continue + } + + newStartBlock, newEndBlock, extractErr := extractAdvisedBlockRange(err.Error()) + if extractErr != nil { + listenerLogger.Errorw("failed to filter logs", "error", err, "extractErr", extractErr, "subID", subID, "startBlock", startBlock, "endBlock", endBlock) + backOffCount.Add(1) + continue + } + startBlock, endBlock = newStartBlock, newEndBlock + listenerLogger.Infow("retrying with advised block range", "subID", subID, "startBlock", startBlock, "endBlock", endBlock) + continue + } + listenerLogger.Infow("fetched historical logs", "subID", subID, "count", len(logs), "startBlock", startBlock, "endBlock", endBlock) + + for _, ethLog := range logs { + if ethLog.BlockNumber == lastBlock && ethLog.Index <= uint(lastIndex) { + listenerLogger.Infow("skipping previously known event", "subID", subID, "blockNumber", ethLog.BlockNumber, "logIndex", ethLog.Index) + continue + } + + historicalCh <- ethLog + } + + startBlock = endBlock + 1 + endBlock += blockStep + } +} + +func extractAdvisedBlockRange(msg string) (startBlock, endBlock uint64, err error) { + if !strings.Contains(msg, "query returned more than 10000 results") { + err = errors.New("error message doesn't contain advised block range") + return + } + + re := regexp.MustCompile(`\[0x([0-9a-fA-F]+), 0x([0-9a-fA-F]+)\]`) + match := re.FindStringSubmatch(msg) + if len(match) != 3 { + err = errors.New("failed to extract block range from error message") + return + } + + startBlock, err = strconv.ParseUint(match[1], 16, 64) + if err != nil { + err = fmt.Errorf("failed to parse block range from error message: %w", err) + return + } + endBlock, err = strconv.ParseUint(match[2], 16, 64) + if err != nil { + err = fmt.Errorf("failed to parse block range from error message: %w", err) + return + } + return +} + +func waitForBackOffTimeout(ctx context.Context, backOffCount int, originator string) bool { + if backOffCount > maxBackOffCount { + listenerLogger.Errorw("back off limit reached, exiting", "originator", originator, "backOffCount", backOffCount) + return true + } + + if backOffCount > 0 { + listenerLogger.Infow("backing off", "originator", originator, "backOffCount", backOffCount) + select { + case <-time.After(time.Duration(2^backOffCount-1) * time.Second): + case <-ctx.Done(): + return false + } + } + return true +} From b636d0e304c78269992b856c0fec7bad01cb1d67 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 14:24:00 +0100 Subject: [PATCH 7/9] build: run `go mod tidy` --- go.mod | 44 +---------------------- go.sum | 111 --------------------------------------------------------- 2 files changed, 1 insertion(+), 154 deletions(-) diff --git a/go.mod b/go.mod index ac4febc..4eac3b4 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/ipfs/go-log/v2 v2.9.1 github.com/layer-3/clearsync v0.0.129 github.com/stretchr/testify v1.11.1 - github.com/testcontainers/testcontainers-go v0.40.0 golang.org/x/sync v0.18.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/sqlite v1.6.0 @@ -16,42 +15,25 @@ require ( ) require ( - dario.cat/mergo v1.0.2 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect - github.com/cpuguy83/dockercfg v0.3.2 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.5.1+incompatible // indirect - github.com/docker/go-connections v0.6.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -66,43 +48,19 @@ require ( github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect - github.com/magiconair/properties v1.8.10 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect - github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/go-archive v0.1.0 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/sequential v0.6.0 // indirect - github.com/moby/sys/user v0.4.0 // indirect - github.com/moby/sys/userns v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/morikuni/aec v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect - github.com/shirou/gopsutil/v4 v4.25.6 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/otel/trace v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect golang.org/x/arch v0.8.0 // indirect diff --git a/go.sum b/go.sum index 4cbcfbd..76fdc6e 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,3 @@ -dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= -dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -22,8 +16,6 @@ github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -46,24 +38,12 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/consensys/gnark-crypto v0.18.0 h1:vIye/FqI50VeAr0B3dx+YjeIvmc3LWz4yEfbWBpTUf0= github.com/consensys/gnark-crypto v0.18.0/go.mod h1:L3mXGFTe1ZN+RSJ+CLjUt9x7PNdx8ubaYfDROyp2Z8c= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= -github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= -github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE3DAvPFkg= github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -77,16 +57,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etly github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= -github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= -github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= -github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A= github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s= github.com/ethereum/c-kzg-4844/v2 v2.1.5 h1:aVtoLK5xwJ6c5RiqO8g8ptJ5KU+2Hdquf6G3aXiHh5s= @@ -97,8 +67,6 @@ github.com/ethereum/go-ethereum v1.16.8 h1:LLLfkZWijhR5m6yrAXbdlTeXoqontH+Ga2f9i github.com/ethereum/go-ethereum v1.16.8/go.mod h1:Fs6QebQbavneQTYcA39PEKv2+zIjX7rPUZ14DER46wk= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= @@ -113,13 +81,7 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -142,7 +104,6 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -154,8 +115,6 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= @@ -200,10 +159,6 @@ github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzW github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= -github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= -github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -220,35 +175,13 @@ github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxd github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= -github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= -github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= -github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= -github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= -github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= -github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= @@ -269,8 +202,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM= github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= @@ -285,19 +216,13 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil/v4 v4.25.6 h1:kLysI2JsKorfaFPcYmcJqbzROzsBWEOAtw6A7dIfqXs= -github.com/shirou/gopsutil/v4 v4.25.6/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -312,8 +237,6 @@ github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe h1:nbdqkIGOG github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= -github.com/testcontainers/testcontainers-go v0.40.0 h1:pSdJYLOVgLE8YdUY2FHQ1Fxu+aMnb6JfVz1mxk7OeMU= -github.com/testcontainers/testcontainers-go v0.40.0/go.mod h1:FSXV5KQtX2HAMlm7U3APNyLkkap35zNLxukw9oBi/MY= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -326,26 +249,6 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -364,9 +267,6 @@ golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -375,19 +275,10 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= -golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= -google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= -google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= -google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -404,7 +295,5 @@ gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= -gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= -gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From 374a533a537a54f286eedae3034e1d1c2c3130c5 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 15:08:52 +0100 Subject: [PATCH 8/9] test(service): add integration test --- custody/types.go | 9 ++ go.mod | 54 +++++++ go.sum | 127 +++++++++++++++ service/integration_test.go | 301 ++++++++++++++++++++++++++++++++++++ service/service.go | 28 ++-- 5 files changed, 509 insertions(+), 10 deletions(-) create mode 100644 service/integration_test.go diff --git a/custody/types.go b/custody/types.go index 4d5c143..3a6c096 100644 --- a/custody/types.go +++ b/custody/types.go @@ -74,3 +74,12 @@ type WithdrawalStore interface { GetTotalWithdrawn(token common.Address, since time.Time) (*big.Int, error) GetTotalWithdrawnByUser(user common.Address, token common.Address, since time.Time) (*big.Int, error) } + +// EthBackend is the Ethereum client interface required by the service. +// Both *ethclient.Client and simulated.Client satisfy this interface. +type EthBackend interface { + bind.ContractBackend + bind.DeployBackend + ChainID(ctx context.Context) (*big.Int, error) + Close() +} diff --git a/go.mod b/go.mod index 4eac3b4..c33a842 100644 --- a/go.mod +++ b/go.mod @@ -15,59 +15,113 @@ require ( ) require ( + github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect github.com/StackExchange/wmi v1.2.1 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.5 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/consensys/gnark-crypto v0.18.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dchest/siphash v1.2.3 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/emicklei/dot v1.6.2 // indirect github.com/ethereum/c-kzg-4844/v2 v2.1.5 // indirect + github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab // indirect github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/ferranbt/fastssz v0.1.4 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.4 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/go-bexpr v0.1.10 // indirect + github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/holiman/uint256 v1.3.2 // indirect + github.com/huin/goupnp v1.3.0 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/stun/v2 v2.0.0 // indirect + github.com/pion/transport/v2 v2.2.1 // indirect + github.com/pion/transport/v3 v3.0.1 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.15.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.42.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/rs/cors v1.7.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/supranational/blst v0.3.16-0.20250831170142-f48500c1fdbe // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.1 // indirect golang.org/x/arch v0.8.0 // indirect golang.org/x/crypto v0.43.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect golang.org/x/net v0.45.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.30.0 // indirect golang.org/x/time v0.9.0 // indirect google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 76fdc6e..612be12 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/StackExchange/wmi v1.2.1 h1:VIkavFPXSjcnS+O8yTq7NI32k0R5Aj+v39y29VYDO github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8= github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= @@ -24,6 +26,8 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= @@ -44,6 +48,7 @@ github.com/crate-crypto/go-eth-kzg v1.4.0 h1:WzDGjHk4gFg6YzV0rJOAsTK4z3Qkz5jd4RE github.com/crate-crypto/go-eth-kzg v1.4.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -69,6 +74,8 @@ github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cn github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/ferranbt/fastssz v0.1.4 h1:OCDB+dYDEQDvAgtAGnTSidK1Pe2tW3nFV40XyMkTeDY= github.com/ferranbt/fastssz v0.1.4/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -81,6 +88,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -100,10 +109,21 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -123,6 +143,7 @@ github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= @@ -141,8 +162,11 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -161,8 +185,10 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= @@ -180,14 +206,25 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -198,6 +235,7 @@ github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -210,8 +248,11 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= +github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= @@ -229,6 +270,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= @@ -249,6 +291,9 @@ github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -258,34 +303,116 @@ go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/service/integration_test.go b/service/integration_test.go new file mode 100644 index 0000000..91ecaef --- /dev/null +++ b/service/integration_test.go @@ -0,0 +1,301 @@ +//go:build !short + +package service_test + +import ( + "context" + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "path/filepath" + "testing" + "time" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/layer-3/nitewatch/config" + "github.com/layer-3/nitewatch/custody" + "github.com/layer-3/nitewatch/service" +) + +const ( + nativeToken = "0x0000000000000000000000000000000000000000" + // Simulated backend always uses chainID 1337. + simChainID = 1337 +) + +type testEnv struct { + sim *simulated.Backend + client custody.EthBackend + keys [4]*ecdsa.PrivateKey + addrs [4]common.Address + auths [4]*bind.TransactOpts + contract *custody.SimpleCustody + addr common.Address +} + +func (e *testEnv) adminAuth() *bind.TransactOpts { return e.auths[0] } +func (e *testEnv) neodaxAuth() *bind.TransactOpts { return e.auths[1] } +func (e *testEnv) nitewatchKey() *ecdsa.PrivateKey { return e.keys[2] } +func (e *testEnv) nitewatchAddr() common.Address { return e.addrs[2] } +func (e *testEnv) userAddr() common.Address { return e.addrs[3] } + +func newTestEnv(t *testing.T) *testEnv { + t.Helper() + + keys := [4]*ecdsa.PrivateKey{} + addrs := [4]common.Address{} + auths := [4]*bind.TransactOpts{} + alloc := make(types.GenesisAlloc) + balance := new(big.Int).Mul(big.NewInt(1000), big.NewInt(1e18)) + + for i := range 4 { + key, err := crypto.GenerateKey() + require.NoError(t, err) + keys[i] = key + addrs[i] = crypto.PubkeyToAddress(key.PublicKey) + auth, err := bind.NewKeyedTransactorWithChainID(key, big.NewInt(simChainID)) + require.NoError(t, err) + auths[i] = auth + alloc[addrs[i]] = types.Account{Balance: balance} + } + + sim := simulated.NewBackend(alloc) + t.Cleanup(func() { sim.Close() }) + + client := simBackendClient{Client: sim.Client(), backend: sim} + + contractAddr, tx, contract, err := custody.DeploySimpleCustody( + auths[0], // admin deploys + client, // backend + addrs[0], // admin + addrs[1], // neodax + addrs[2], // nitewatch + ) + require.NoError(t, err) + sim.Commit() + + receipt, err := client.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, uint64(1), receipt.Status, "contract deployment failed") + + return &testEnv{ + sim: sim, + client: client, + keys: keys, + addrs: addrs, + auths: auths, + contract: contract, + addr: contractAddr, + } +} + +// simBackendClient wraps simulated.Client to add Close(), satisfying custody.EthBackend. +type simBackendClient struct { + simulated.Client + backend *simulated.Backend +} + +func (c simBackendClient) Close() { c.backend.Close() } + +// autoCommit mines blocks periodically so that bind.WaitMined can return. +func autoCommit(ctx context.Context, sim *simulated.Backend, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + sim.Commit() + } + } +} + +func createNitewatchService(t *testing.T, env *testEnv, limitWei string) *service.Service { + t.Helper() + + conf := config.Config{ + Blockchain: config.BlockchainConfig{ + ContractAddr: env.addr.Hex(), + PrivateKey: fmt.Sprintf("%x", crypto.FromECDSA(env.nitewatchKey())), + }, + Limits: config.LimitsConfig{ + nativeToken: config.LimitConfig{ + Hourly: limitWei, + Daily: limitWei, + }, + }, + DBPath: filepath.Join(t.TempDir(), "nitewatch.db"), + ListenAddr: ":0", + } + + svc, err := service.NewWithBackend(conf, env.client) + require.NoError(t, err) + return svc +} + +func runNitewatchService(t *testing.T, svc *service.Service) { + t.Helper() + + ctx, cancel := context.WithCancel(t.Context()) + errCh := make(chan error, 1) + go func() { errCh <- svc.RunWorkerWithContext(ctx) }() + + require.Eventually(t, svc.IsWorkerReady, 30*time.Second, 100*time.Millisecond, + "nitewatch worker did not become ready") + + t.Cleanup(func() { + cancel() + select { + case err := <-errCh: + if err != nil && !errors.Is(err, context.Canceled) { + t.Logf("nitewatch worker stopped with error: %v", err) + } + case <-time.After(10 * time.Second): + t.Log("nitewatch worker cleanup timeout") + } + }) +} + +// waitForWithdrawFinalized polls for a WithdrawFinalized event with the given success value. +func waitForWithdrawFinalized(t *testing.T, env *testEnv, timeout time.Duration) *custody.SimpleCustodyWithdrawFinalized { + t.Helper() + + deadline := time.After(timeout) + ticker := time.NewTicker(200 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-deadline: + t.Fatal("timed out waiting for WithdrawFinalized event") + return nil + case <-ticker.C: + iter, err := env.contract.FilterWithdrawFinalized(&bind.FilterOpts{ + Start: 0, + Context: context.Background(), + }, nil) + require.NoError(t, err) + if iter.Next() { + ev := iter.Event + iter.Close() + return ev + } + iter.Close() + } + } +} + +func TestWithdrawalFinalized(t *testing.T) { + env := newTestEnv(t) + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + go autoCommit(ctx, env.sim, 100*time.Millisecond) + + // 100 ETH limit — well above our 0.5 ETH withdrawal. + svc := createNitewatchService(t, env, "100000000000000000000") + runNitewatchService(t, svc) + + // User deposits 1 ETH. + depositAmount := big.NewInt(1e18) + userAuth := copyAuth(env.auths[3]) + userAuth.Value = depositAmount + tx, err := env.contract.Deposit(userAuth, common.Address{}, depositAmount) + require.NoError(t, err) + env.sim.Commit() + receipt, err := env.client.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, uint64(1), receipt.Status, "deposit tx failed") + + // Record user balance before withdrawal. + userBalBefore, err := env.client.(ethereum.ChainStateReader).BalanceAt( + context.Background(), env.userAddr(), nil) + require.NoError(t, err) + + // NeoDAX starts a 0.5 ETH withdrawal for user. + withdrawAmount := new(big.Int).Div(depositAmount, big.NewInt(2)) + neodaxAuth := copyAuth(env.neodaxAuth()) + tx, err = env.contract.StartWithdraw(neodaxAuth, env.userAddr(), common.Address{}, withdrawAmount, big.NewInt(1)) + require.NoError(t, err) + env.sim.Commit() + receipt, err = env.client.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, uint64(1), receipt.Status, "startWithdraw tx failed") + + // Wait for nitewatch to finalize the withdrawal. + ev := waitForWithdrawFinalized(t, env, 30*time.Second) + assert.True(t, ev.Success, "expected withdrawal to be finalized successfully") + + // Verify user received funds. + userBalAfter, err := env.client.(ethereum.ChainStateReader).BalanceAt( + context.Background(), env.userAddr(), nil) + require.NoError(t, err) + expected := new(big.Int).Add(userBalBefore, withdrawAmount) + assert.Equal(t, expected.String(), userBalAfter.String(), + "user balance should increase by the withdrawn amount") +} + +func TestWithdrawalRejected(t *testing.T) { + env := newTestEnv(t) + + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + go autoCommit(ctx, env.sim, 100*time.Millisecond) + + // 0.1 ETH limit — below our 0.5 ETH withdrawal. + svc := createNitewatchService(t, env, "100000000000000000") + runNitewatchService(t, svc) + + // User deposits 1 ETH. + depositAmount := big.NewInt(1e18) + userAuth := copyAuth(env.auths[3]) + userAuth.Value = depositAmount + tx, err := env.contract.Deposit(userAuth, common.Address{}, depositAmount) + require.NoError(t, err) + env.sim.Commit() + receipt, err := env.client.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, uint64(1), receipt.Status, "deposit tx failed") + + // Record user balance before withdrawal. + userBalBefore, err := env.client.(ethereum.ChainStateReader).BalanceAt( + context.Background(), env.userAddr(), nil) + require.NoError(t, err) + + // NeoDAX starts a 0.5 ETH withdrawal (exceeds 0.1 ETH limit). + withdrawAmount := new(big.Int).Div(depositAmount, big.NewInt(2)) + neodaxAuth := copyAuth(env.neodaxAuth()) + tx, err = env.contract.StartWithdraw(neodaxAuth, env.userAddr(), common.Address{}, withdrawAmount, big.NewInt(1)) + require.NoError(t, err) + env.sim.Commit() + receipt, err = env.client.TransactionReceipt(context.Background(), tx.Hash()) + require.NoError(t, err) + require.Equal(t, uint64(1), receipt.Status, "startWithdraw tx failed") + + // Wait for nitewatch to reject the withdrawal. + ev := waitForWithdrawFinalized(t, env, 30*time.Second) + assert.False(t, ev.Success, "expected withdrawal to be rejected") + + // Verify user balance unchanged. + userBalAfter, err := env.client.(ethereum.ChainStateReader).BalanceAt( + context.Background(), env.userAddr(), nil) + require.NoError(t, err) + assert.Equal(t, userBalBefore.String(), userBalAfter.String(), + "user balance should not change after rejection") +} + +// copyAuth creates a shallow copy of TransactOpts so concurrent uses don't race. +func copyAuth(auth *bind.TransactOpts) *bind.TransactOpts { + cp := *auth + return &cp +} diff --git a/service/service.go b/service/service.go index 918c7c5..5adb0f0 100644 --- a/service/service.go +++ b/service/service.go @@ -48,7 +48,7 @@ type Service struct { Logger *slog.Logger web *httpServer - ethClient *ethclient.Client + ethClient custody.EthBackend contract *custody.IWithdraw listener *custody.Listener auth *bind.TransactOpts @@ -58,7 +58,24 @@ type Service struct { workerReady int32 } +// New creates a Service that dials an Ethereum node via the configured RPC URL. func New(conf config.Config) (*Service, error) { + client, err := ethclient.Dial(conf.Blockchain.RPCURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to Ethereum RPC: %w", err) + } + + svc, err := NewWithBackend(conf, client) + if err != nil { + client.Close() + return nil, err + } + return svc, nil +} + +// NewWithBackend creates a Service using a pre-existing Ethereum backend. +// The caller is responsible for closing the backend when done. +func NewWithBackend(conf config.Config, client custody.EthBackend) (*Service, error) { logger := slog.New(slog.NewTextHandler(os.Stderr, nil)).With("service", "nitewatch") srv := newHTTPServer(conf.ListenAddr) @@ -85,33 +102,24 @@ func New(conf config.Config) (*Service, error) { chk := checker.New(globalLimits, userOverrides, db) - client, err := ethclient.Dial(conf.Blockchain.RPCURL) - if err != nil { - return nil, fmt.Errorf("failed to connect to Ethereum RPC: %w", err) - } - chainID, err := client.ChainID(context.Background()) if err != nil { - client.Close() return nil, fmt.Errorf("failed to get chain ID: %w", err) } key, err := crypto.HexToECDSA(conf.Blockchain.PrivateKey) if err != nil { - client.Close() return nil, fmt.Errorf("failed to parse private key: %w", err) } auth, err := bind.NewKeyedTransactorWithChainID(key, chainID) if err != nil { - client.Close() return nil, fmt.Errorf("failed to create transactor: %w", err) } addr := common.HexToAddress(conf.Blockchain.ContractAddr) withdrawContract, err := custody.NewIWithdraw(addr, client) if err != nil { - client.Close() return nil, fmt.Errorf("failed to bind IWithdraw contract: %w", err) } From b9a61c80007366ecb95bb4a7f56a7a8b98c77198 Mon Sep 17 00:00:00 2001 From: Max Pushkarov Date: Thu, 19 Feb 2026 15:13:05 +0100 Subject: [PATCH 9/9] ci: read Go version from go.mod instead of hardcoding --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a71e982..30efead 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,8 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.25.6" + go-version-file: go.mod + cache-dependency-path: go.sum - name: Run Go tests run: go test ./...