From 1602763599b446eaad5211b75afae59f2ae1c518 Mon Sep 17 00:00:00 2001 From: Bojidar Marinov Date: Thu, 12 Dec 2024 12:15:55 -0600 Subject: [PATCH] feat(wip): Add payment/deposit support to the frontend --- package.json | 3 +- pkg/abi-ts/generated.ts | 269 ++++++++++++++++++ pkg/abi-ts/wagmi.config.ts | 6 +- pkg/abi/IERC20.abi.go | 149 ++++++++-- pkg/proto-ts/deployment_pb.ts | 2 +- pkg/proto-ts/pod_pb.ts | 20 +- pkg/proto-ts/provision-pod_connect.ts | 13 +- pkg/proto-ts/provision-pod_pb.ts | 82 +----- pkg/proto/protoconnect/autoscaler.connect.go | 21 +- pkg/proto/protoconnect/interceptors.go | 68 +++-- .../protoconnect/provision-pod.connect.go | 35 +-- pnpm-lock.yaml | 3 + storage/frontend/package.json | 1 + storage/frontend/src/App.css | 45 +++ storage/frontend/src/App.tsx | 85 ++++-- storage/frontend/src/contracts.ts | 158 ++++++++++ 16 files changed, 752 insertions(+), 208 deletions(-) create mode 100644 storage/frontend/src/App.css create mode 100644 storage/frontend/src/contracts.ts diff --git a/package.json b/package.json index 108d3c62..e507480b 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,6 @@ "eslint-plugin-n": "^16.3.1", "eslint-plugin-promise": "^6.1.1", "typescript": "^5.3.2" - } + }, + "packageManager": "pnpm@9.12.2" } diff --git a/pkg/abi-ts/generated.ts b/pkg/abi-ts/generated.ts index 21f34fd2..4b1a578b 100644 --- a/pkg/abi-ts/generated.ts +++ b/pkg/abi-ts/generated.ts @@ -531,3 +531,272 @@ export const paymentAbi = [ name: 'SafeERC20FailedOperation' } ] as const + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// PaymentV2 +////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +export const paymentV2Abi = [ + { + type: 'constructor', + inputs: [ + { name: '_token', internalType: 'contract IERC20', type: 'address' }, + { name: '_unlockTime', internalType: 'uint256', type: 'uint256' } + ], + stateMutability: 'nonpayable' + }, + { + type: 'function', + inputs: [ + { name: 'channelId', internalType: 'bytes32', type: 'bytes32' }, + { name: 'recipient', internalType: 'address', type: 'address' }, + { name: 'permissions', internalType: 'uint16', type: 'uint16' }, + { name: 'reservedAmount', internalType: 'uint256', type: 'uint256' }, + { name: 'extraLimit', internalType: 'uint256', type: 'uint256' } + ], + name: 'authorize', + outputs: [], + stateMutability: 'nonpayable' + }, + { + type: 'function', + inputs: [ + { name: '', internalType: 'bytes32', type: 'bytes32' }, + { name: '', internalType: 'address', type: 'address' } + ], + name: 'channelAuthorizations', + outputs: [ + { name: 'reservation', internalType: 'uint128', type: 'uint128' }, + { name: 'limit', internalType: 'uint128', type: 'uint128' }, + { name: 'unlocksAt', internalType: 'uint240', type: 'uint240' }, + { name: 'permissions', internalType: 'uint16', type: 'uint16' } + ], + stateMutability: 'view' + }, + { + type: 'function', + inputs: [{ name: '', internalType: 'bytes32', type: 'bytes32' }], + name: 'channels', + outputs: [ + { name: 'available', internalType: 'uint128', type: 'uint128' }, + { name: 'reserved', internalType: 'uint128', type: 'uint128' } + ], + stateMutability: 'view' + }, + { + type: 'function', + inputs: [ + { + name: 'channelDiscriminator', + internalType: 'bytes32', + type: 'bytes32' + }, + { name: 'initialAmount', internalType: 'uint256', type: 'uint256' } + ], + name: 'create', + outputs: [{ name: 'channelId', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'nonpayable' + }, + { + type: 'function', + inputs: [ + { + name: 'channelDiscriminator', + internalType: 'bytes32', + type: 'bytes32' + }, + { name: 'initialAmount', internalType: 'uint256', type: 'uint256' }, + { name: 'recipient', internalType: 'address', type: 'address' }, + { name: 'permissions', internalType: 'uint16', type: 'uint16' }, + { name: 'reservedAmount', internalType: 'uint256', type: 'uint256' }, + { name: 'limit', internalType: 'uint256', type: 'uint256' } + ], + name: 'createAndAuthorize', + outputs: [], + stateMutability: 'nonpayable' + }, + { + type: 'function', + inputs: [ + { name: 'channelId', internalType: 'bytes32', type: 'bytes32' }, + { name: 'amount', internalType: 'uint256', type: 'uint256' } + ], + name: 'deposit', + outputs: [], + stateMutability: 'nonpayable' + }, + { + type: 'function', + inputs: [ + { name: 'creator', internalType: 'address', type: 'address' }, + { name: 'channelDiscriminator', internalType: 'bytes32', type: 'bytes32' } + ], + name: 'getChannelId', + outputs: [{ name: 'channelId', internalType: 'bytes32', type: 'bytes32' }], + stateMutability: 'pure' + }, + { + type: 'function', + inputs: [], + name: 'token', + outputs: [{ name: '', internalType: 'contract IERC20', type: 'address' }], + stateMutability: 'view' + }, + { + type: 'function', + inputs: [ + { name: 'channelId', internalType: 'bytes32', type: 'bytes32' }, + { name: 'recipient', internalType: 'address', type: 'address' } + ], + name: 'unlock', + outputs: [], + stateMutability: 'nonpayable' + }, + { + type: 'function', + inputs: [], + name: 'unlockTime', + outputs: [{ name: '', internalType: 'uint256', type: 'uint256' }], + stateMutability: 'view' + }, + { + type: 'function', + inputs: [ + { name: 'channelId', internalType: 'bytes32', type: 'bytes32' }, + { name: 'transferAddress', internalType: 'address', type: 'address' }, + { name: 'transferAmount', internalType: 'uint256', type: 'uint256' } + ], + name: 'withdraw', + outputs: [], + stateMutability: 'nonpayable' + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'channelId', + internalType: 'bytes32', + type: 'bytes32', + indexed: true + }, + { + name: 'recipient', + internalType: 'address', + type: 'address', + indexed: true + }, + { + name: 'permissions', + internalType: 'uint16', + type: 'uint16', + indexed: false + }, + { + name: 'reservedAmount', + internalType: 'uint256', + type: 'uint256', + indexed: false + }, + { + name: 'limit', + internalType: 'uint256', + type: 'uint256', + indexed: false + } + ], + name: 'Authorize' + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'channelId', + internalType: 'bytes32', + type: 'bytes32', + indexed: true + }, + { + name: 'payer', + internalType: 'address', + type: 'address', + indexed: true + }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false + } + ], + name: 'Deposit' + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'channelId', + internalType: 'bytes32', + type: 'bytes32', + indexed: true + }, + { + name: 'recipient', + internalType: 'address', + type: 'address', + indexed: true + } + ], + name: 'Unlock' + }, + { + type: 'event', + anonymous: false, + inputs: [ + { + name: 'channelId', + internalType: 'bytes32', + type: 'bytes32', + indexed: true + }, + { + name: 'recipient', + internalType: 'address', + type: 'address', + indexed: true + }, + { + name: 'amount', + internalType: 'uint256', + type: 'uint256', + indexed: false + } + ], + name: 'Withdraw' + }, + { + type: 'error', + inputs: [{ name: 'target', internalType: 'address', type: 'address' }], + name: 'AddressEmptyCode' + }, + { type: 'error', inputs: [], name: 'AlreadyInitialized' }, + { type: 'error', inputs: [], name: 'AuthorizationLocked' }, + { type: 'error', inputs: [], name: 'FailedCall' }, + { + type: 'error', + inputs: [ + { name: 'balance', internalType: 'uint256', type: 'uint256' }, + { name: 'needed', internalType: 'uint256', type: 'uint256' } + ], + name: 'InsufficientBalance' + }, + { type: 'error', inputs: [], name: 'InsufficientFunds' }, + { type: 'error', inputs: [], name: 'NotAuthorized' }, + { + type: 'error', + inputs: [{ name: 'token', internalType: 'address', type: 'address' }], + name: 'SafeERC20FailedOperation' + } +] as const diff --git a/pkg/abi-ts/wagmi.config.ts b/pkg/abi-ts/wagmi.config.ts index d8392652..6a55c82b 100644 --- a/pkg/abi-ts/wagmi.config.ts +++ b/pkg/abi-ts/wagmi.config.ts @@ -12,7 +12,11 @@ export default defineConfig({ forge: { build: false }, - include: ['Payment.json', 'MockToken.json'] + include: [ + 'Payment.json', + 'MockToken.json', + 'PaymentV2.json', + ] }) ] }) diff --git a/pkg/abi/IERC20.abi.go b/pkg/abi/IERC20.abi.go index 31ac79fa..1ed492ae 100644 --- a/pkg/abi/IERC20.abi.go +++ b/pkg/abi/IERC20.abi.go @@ -31,7 +31,7 @@ var ( // IERC20MetaData contains all meta data concerning the IERC20 contract. var IERC20MetaData = &bind.MetaData{ - ABI: "[{\"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\":\"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}]", + ABI: "[{\"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\":\"amount\",\"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\":\"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\":\"amount\",\"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\":\"amount\",\"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}]", } // IERC20ABI is the input ABI used to generate the binding from. @@ -242,6 +242,99 @@ func (_IERC20 *IERC20CallerSession) BalanceOf(account common.Address) (*big.Int, return _IERC20.Contract.BalanceOf(&_IERC20.CallOpts, account) } +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_IERC20 *IERC20Caller) Decimals(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _IERC20.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 (_IERC20 *IERC20Session) Decimals() (uint8, error) { + return _IERC20.Contract.Decimals(&_IERC20.CallOpts) +} + +// Decimals is a free data retrieval call binding the contract method 0x313ce567. +// +// Solidity: function decimals() view returns(uint8) +func (_IERC20 *IERC20CallerSession) Decimals() (uint8, error) { + return _IERC20.Contract.Decimals(&_IERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_IERC20 *IERC20Caller) Name(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _IERC20.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 (_IERC20 *IERC20Session) Name() (string, error) { + return _IERC20.Contract.Name(&_IERC20.CallOpts) +} + +// Name is a free data retrieval call binding the contract method 0x06fdde03. +// +// Solidity: function name() view returns(string) +func (_IERC20 *IERC20CallerSession) Name() (string, error) { + return _IERC20.Contract.Name(&_IERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_IERC20 *IERC20Caller) Symbol(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _IERC20.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 (_IERC20 *IERC20Session) Symbol() (string, error) { + return _IERC20.Contract.Symbol(&_IERC20.CallOpts) +} + +// Symbol is a free data retrieval call binding the contract method 0x95d89b41. +// +// Solidity: function symbol() view returns(string) +func (_IERC20 *IERC20CallerSession) Symbol() (string, error) { + return _IERC20.Contract.Symbol(&_IERC20.CallOpts) +} + // TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. // // Solidity: function totalSupply() view returns(uint256) @@ -275,65 +368,65 @@ func (_IERC20 *IERC20CallerSession) TotalSupply() (*big.Int, error) { // Approve is a paid mutator transaction binding the contract method 0x095ea7b3. // -// Solidity: function approve(address spender, uint256 value) returns(bool) -func (_IERC20 *IERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.contract.Transact(opts, "approve", spender, value) +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_IERC20 *IERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.contract.Transact(opts, "approve", spender, amount) } // Approve is a paid mutator transaction binding the contract method 0x095ea7b3. // -// Solidity: function approve(address spender, uint256 value) returns(bool) -func (_IERC20 *IERC20Session) Approve(spender common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.Contract.Approve(&_IERC20.TransactOpts, spender, value) +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_IERC20 *IERC20Session) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.Contract.Approve(&_IERC20.TransactOpts, spender, amount) } // Approve is a paid mutator transaction binding the contract method 0x095ea7b3. // -// Solidity: function approve(address spender, uint256 value) returns(bool) -func (_IERC20 *IERC20TransactorSession) Approve(spender common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.Contract.Approve(&_IERC20.TransactOpts, spender, value) +// Solidity: function approve(address spender, uint256 amount) returns(bool) +func (_IERC20 *IERC20TransactorSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.Contract.Approve(&_IERC20.TransactOpts, spender, amount) } // Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. // -// Solidity: function transfer(address to, uint256 value) returns(bool) -func (_IERC20 *IERC20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.contract.Transact(opts, "transfer", to, value) +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_IERC20 *IERC20Transactor) Transfer(opts *bind.TransactOpts, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.contract.Transact(opts, "transfer", to, amount) } // Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. // -// Solidity: function transfer(address to, uint256 value) returns(bool) -func (_IERC20 *IERC20Session) Transfer(to common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.Contract.Transfer(&_IERC20.TransactOpts, to, value) +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_IERC20 *IERC20Session) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.Contract.Transfer(&_IERC20.TransactOpts, to, amount) } // Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. // -// Solidity: function transfer(address to, uint256 value) returns(bool) -func (_IERC20 *IERC20TransactorSession) Transfer(to common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.Contract.Transfer(&_IERC20.TransactOpts, to, value) +// Solidity: function transfer(address to, uint256 amount) returns(bool) +func (_IERC20 *IERC20TransactorSession) Transfer(to common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.Contract.Transfer(&_IERC20.TransactOpts, to, amount) } // TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. // -// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) -func (_IERC20 *IERC20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.contract.Transact(opts, "transferFrom", from, to, value) +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_IERC20 *IERC20Transactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.contract.Transact(opts, "transferFrom", from, to, amount) } // TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. // -// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) -func (_IERC20 *IERC20Session) TransferFrom(from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.Contract.TransferFrom(&_IERC20.TransactOpts, from, to, value) +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_IERC20 *IERC20Session) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.Contract.TransferFrom(&_IERC20.TransactOpts, from, to, amount) } // TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. // -// Solidity: function transferFrom(address from, address to, uint256 value) returns(bool) -func (_IERC20 *IERC20TransactorSession) TransferFrom(from common.Address, to common.Address, value *big.Int) (*types.Transaction, error) { - return _IERC20.Contract.TransferFrom(&_IERC20.TransactOpts, from, to, value) +// Solidity: function transferFrom(address from, address to, uint256 amount) returns(bool) +func (_IERC20 *IERC20TransactorSession) TransferFrom(from common.Address, to common.Address, amount *big.Int) (*types.Transaction, error) { + return _IERC20.Contract.TransferFrom(&_IERC20.TransactOpts, from, to, amount) } // IERC20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the IERC20 contract. diff --git a/pkg/proto-ts/deployment_pb.ts b/pkg/proto-ts/deployment_pb.ts index f4935134..3c1841bc 100644 --- a/pkg/proto-ts/deployment_pb.ts +++ b/pkg/proto-ts/deployment_pb.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -// @generated by protoc-gen-es v1.6.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.10.0 with parameter "target=ts" // @generated from file deployment.proto (package apocryph.proto.v0.deployment, syntax proto3) /* eslint-disable */ // @ts-nocheck diff --git a/pkg/proto-ts/pod_pb.ts b/pkg/proto-ts/pod_pb.ts index 23834532..c7b00346 100644 --- a/pkg/proto-ts/pod_pb.ts +++ b/pkg/proto-ts/pod_pb.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -// @generated by protoc-gen-es v1.6.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.10.0 with parameter "target=ts" // @generated from file pod.proto (package apocryph.proto.v0.pod, syntax proto3) /* eslint-disable */ // @ts-nocheck @@ -817,19 +817,19 @@ export class KeyPair extends Message { */ export class VerificationSettings extends Message { /** - * @generated from field: bool ForcePolicy = 1; + * @generated from field: bool forcePolicy = 1; */ - ForcePolicy = false; + forcePolicy = false; /** - * @generated from field: bool PublicVerifiability = 2; + * @generated from field: bool publicVerifiability = 2; */ - PublicVerifiability = false; + publicVerifiability = false; /** - * @generated from field: string VerificationHost = 3; + * @generated from field: string verificationHost = 3; */ - VerificationHost = ""; + verificationHost = ""; constructor(data?: PartialMessage) { super(); @@ -839,9 +839,9 @@ export class VerificationSettings extends Message { static readonly runtime: typeof proto3 = proto3; static readonly typeName = "apocryph.proto.v0.pod.VerificationSettings"; static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "ForcePolicy", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, - { no: 2, name: "PublicVerifiability", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, - { no: 3, name: "VerificationHost", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 1, name: "forcePolicy", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 2, name: "publicVerifiability", kind: "scalar", T: 8 /* ScalarType.BOOL */ }, + { no: 3, name: "verificationHost", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): VerificationSettings { diff --git a/pkg/proto-ts/provision-pod_connect.ts b/pkg/proto-ts/provision-pod_connect.ts index 739c872a..9e8f5f86 100644 --- a/pkg/proto-ts/provision-pod_connect.ts +++ b/pkg/proto-ts/provision-pod_connect.ts @@ -1,11 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 -// @generated by protoc-gen-connect-es v1.2.0 with parameter "target=ts" +// @generated by protoc-gen-connect-es v1.6.1 with parameter "target=ts" // @generated from file provision-pod.proto (package apocryph.proto.v0.provisionPod, syntax proto3) /* eslint-disable */ // @ts-nocheck -import { DeletePodRequest, DeletePodResponse, PodInfoRequest, PodInfoResponse, PodLogRequest, PodLogResponse, ProvisionPodRequest, ProvisionPodResponse, UpdatePodRequest } from "./provision-pod_pb.js"; +import { DeletePodRequest, DeletePodResponse, PodLogRequest, PodLogResponse, ProvisionPodRequest, ProvisionPodResponse, UpdatePodRequest } from "./provision-pod_pb.js"; import { MethodKind } from "@bufbuild/protobuf"; /** @@ -41,15 +41,6 @@ export const ProvisionPodService = { O: DeletePodResponse, kind: MethodKind.Unary, }, - /** - * @generated from rpc apocryph.proto.v0.provisionPod.ProvisionPodService.GetPodInfos - */ - getPodInfos: { - name: "GetPodInfos", - I: PodInfoRequest, - O: PodInfoResponse, - kind: MethodKind.Unary, - }, /** * @generated from rpc apocryph.proto.v0.provisionPod.ProvisionPodService.GetPodLogs */ diff --git a/pkg/proto-ts/provision-pod_pb.ts b/pkg/proto-ts/provision-pod_pb.ts index 42910508..ce210617 100644 --- a/pkg/proto-ts/provision-pod_pb.ts +++ b/pkg/proto-ts/provision-pod_pb.ts @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0 -// @generated by protoc-gen-es v1.6.0 with parameter "target=ts" +// @generated by protoc-gen-es v1.10.0 with parameter "target=ts" // @generated from file provision-pod.proto (package apocryph.proto.v0.provisionPod, syntax proto3) /* eslint-disable */ // @ts-nocheck @@ -292,6 +292,11 @@ export class ProvisionPodResponse extends Message { */ namespace = ""; + /** + * @generated from field: string verificationHost = 4; + */ + verificationHost = ""; + constructor(data?: PartialMessage) { super(); proto3.util.initPartial(data, this); @@ -303,6 +308,7 @@ export class ProvisionPodResponse extends Message { { no: 1, name: "error", kind: "scalar", T: 9 /* ScalarType.STRING */ }, { no: 2, name: "addresses", kind: "message", T: ProvisionPodResponse_ExposedHostPort, repeated: true }, { no: 3, name: "namespace", kind: "scalar", T: 9 /* ScalarType.STRING */ }, + { no: 4, name: "verificationHost", kind: "scalar", T: 9 /* ScalarType.STRING */ }, ]); static fromBinary(bytes: Uint8Array, options?: Partial): ProvisionPodResponse { @@ -488,77 +494,3 @@ export class LogEntry extends Message { } } -/** - * @generated from message apocryph.proto.v0.provisionPod.PodInfoRequest - */ -export class PodInfoRequest extends Message { - /** - * @generated from field: string namespace = 1; - */ - namespace = ""; - - constructor(data?: PartialMessage) { - super(); - proto3.util.initPartial(data, this); - } - - static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "apocryph.proto.v0.provisionPod.PodInfoRequest"; - static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "namespace", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - ]); - - static fromBinary(bytes: Uint8Array, options?: Partial): PodInfoRequest { - return new PodInfoRequest().fromBinary(bytes, options); - } - - static fromJson(jsonValue: JsonValue, options?: Partial): PodInfoRequest { - return new PodInfoRequest().fromJson(jsonValue, options); - } - - static fromJsonString(jsonString: string, options?: Partial): PodInfoRequest { - return new PodInfoRequest().fromJsonString(jsonString, options); - } - - static equals(a: PodInfoRequest | PlainMessage | undefined, b: PodInfoRequest | PlainMessage | undefined): boolean { - return proto3.util.equals(PodInfoRequest, a, b); - } -} - -/** - * @generated from message apocryph.proto.v0.provisionPod.PodInfoResponse - */ -export class PodInfoResponse extends Message { - /** - * @generated from field: string info = 1; - */ - info = ""; - - constructor(data?: PartialMessage) { - super(); - proto3.util.initPartial(data, this); - } - - static readonly runtime: typeof proto3 = proto3; - static readonly typeName = "apocryph.proto.v0.provisionPod.PodInfoResponse"; - static readonly fields: FieldList = proto3.util.newFieldList(() => [ - { no: 1, name: "info", kind: "scalar", T: 9 /* ScalarType.STRING */ }, - ]); - - static fromBinary(bytes: Uint8Array, options?: Partial): PodInfoResponse { - return new PodInfoResponse().fromBinary(bytes, options); - } - - static fromJson(jsonValue: JsonValue, options?: Partial): PodInfoResponse { - return new PodInfoResponse().fromJson(jsonValue, options); - } - - static fromJsonString(jsonString: string, options?: Partial): PodInfoResponse { - return new PodInfoResponse().fromJsonString(jsonString, options); - } - - static equals(a: PodInfoResponse | PlainMessage | undefined, b: PodInfoResponse | PlainMessage | undefined): boolean { - return proto3.util.equals(PodInfoResponse, a, b); - } -} - diff --git a/pkg/proto/protoconnect/autoscaler.connect.go b/pkg/proto/protoconnect/autoscaler.connect.go index 14e0f39b..fb42878f 100644 --- a/pkg/proto/protoconnect/autoscaler.connect.go +++ b/pkg/proto/protoconnect/autoscaler.connect.go @@ -20,7 +20,7 @@ import ( // generated with a version of connect newer than the one compiled into your binary. You can fix the // problem by either regenerating this code with an older version of connect or updating the connect // version compiled into your binary. -const _ = connect.IsAtLeastVersion1_13_0 +const _ = connect.IsAtLeastVersion0_1_0 const ( // AutoscalerServiceName is the fully-qualified name of the AutoscalerService service. @@ -43,13 +43,6 @@ const ( AutoscalerServiceTriggerNodeProcedure = "/apocryph.proto.v0.autoscaler.AutoscalerService/TriggerNode" ) -// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. -var ( - autoscalerServiceServiceDescriptor = proto.File_autoscaler_proto.Services().ByName("AutoscalerService") - autoscalerServiceConnectClusterMethodDescriptor = autoscalerServiceServiceDescriptor.Methods().ByName("ConnectCluster") - autoscalerServiceTriggerNodeMethodDescriptor = autoscalerServiceServiceDescriptor.Methods().ByName("TriggerNode") -) - // AutoscalerServiceClient is a client for the apocryph.proto.v0.autoscaler.AutoscalerService // service. type AutoscalerServiceClient interface { @@ -71,14 +64,12 @@ func NewAutoscalerServiceClient(httpClient connect.HTTPClient, baseURL string, o connectCluster: connect.NewClient[proto.ConnectClusterRequest, proto.ConnectClusterResponse]( httpClient, baseURL+AutoscalerServiceConnectClusterProcedure, - connect.WithSchema(autoscalerServiceConnectClusterMethodDescriptor), - connect.WithClientOptions(opts...), + opts..., ), triggerNode: connect.NewClient[proto.ConnectClusterRequest, proto.TriggerNodeResponse]( httpClient, baseURL+AutoscalerServiceTriggerNodeProcedure, - connect.WithSchema(autoscalerServiceTriggerNodeMethodDescriptor), - connect.WithClientOptions(opts...), + opts..., ), } } @@ -115,14 +106,12 @@ func NewAutoscalerServiceHandler(svc AutoscalerServiceHandler, opts ...connect.H autoscalerServiceConnectClusterHandler := connect.NewUnaryHandler( AutoscalerServiceConnectClusterProcedure, svc.ConnectCluster, - connect.WithSchema(autoscalerServiceConnectClusterMethodDescriptor), - connect.WithHandlerOptions(opts...), + opts..., ) autoscalerServiceTriggerNodeHandler := connect.NewUnaryHandler( AutoscalerServiceTriggerNodeProcedure, svc.TriggerNode, - connect.WithSchema(autoscalerServiceTriggerNodeMethodDescriptor), - connect.WithHandlerOptions(opts...), + opts..., ) return "/apocryph.proto.v0.autoscaler.AutoscalerService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { diff --git a/pkg/proto/protoconnect/interceptors.go b/pkg/proto/protoconnect/interceptors.go index fd425d0b..b018665c 100644 --- a/pkg/proto/protoconnect/interceptors.go +++ b/pkg/proto/protoconnect/interceptors.go @@ -104,49 +104,60 @@ func (a authInterceptor) authenticate(header http.Header) (common.Address, error if !ok { return common.Address{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Expected Bearer Authentication")) } + + // verify signature and expiration of token + token, err := ValidateToken(tokenString) + if err != nil { + return common.Address{}, err + } + + // verify publisherAddress in namespace is same one signed in token + ns := header.Get(NamespaceHeader) + nsExpected := NamespaceFromTokenParts(token.Publisher, token.PodId) + if ns == "" { + header.Set(NamespaceHeader, nsExpected) + } else if ns != nsExpected { + return common.Address{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Invalid namespace")) + } + + return token.Publisher, nil +} + +func ValidateToken(tokenString string) (*Token, error) { tokenParts := strings.Split(tokenString, ".") if len(tokenParts) != 2 { - return common.Address{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Invalid token (wrong number of parts)")) + return nil, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Invalid token (wrong number of parts)")) } tokenData, err := base64.StdEncoding.DecodeString(tokenParts[0]) if err != nil { - return common.Address{}, err + return nil, err } tokenSignature, err := base64.StdEncoding.DecodeString(tokenParts[1]) if err != nil { - return common.Address{}, err + return nil, err } // verify if token has exired or not token := &Token{} err = json.Unmarshal(tokenData, token) if err != nil { - return common.Address{}, connect.NewError(connect.CodeDataLoss, fmt.Errorf("Failed Unmarshalling token")) + return nil, connect.NewError(connect.CodeDataLoss, fmt.Errorf("Failed Unmarshalling token")) } if time.Now().UTC().After(token.ExpirationTime) { - return common.Address{}, connect.NewError(connect.CodeDeadlineExceeded, fmt.Errorf("Token Expired")) + return nil, connect.NewError(connect.CodeDeadlineExceeded, fmt.Errorf("Token Expired")) } // Verify Signature valid, err := VerifyPayload(token.Publisher, tokenData, tokenSignature) if err != nil { - return common.Address{}, fmt.Errorf("Error verifying payload: %w", err) + return nil, fmt.Errorf("Error verifying payload: %w", err) } if !valid { - return common.Address{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Invalid signature")) - } - - // verify publisherAddress in namespace is same one signed in token - ns := header.Get(NamespaceHeader) - nsExpected := NamespaceFromTokenParts(token.Publisher, token.PodId) - if ns == "" { - header.Set(NamespaceHeader, nsExpected) - } else if ns != nsExpected { - return common.Address{}, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Invalid namespace")) + return nil, connect.NewError(connect.CodeUnauthenticated, fmt.Errorf("Invalid signature")) } - return token.Publisher, nil + return token, nil } func NamespaceFromTokenParts(publisher common.Address, podId common.Hash) string { @@ -215,22 +226,35 @@ func (a *AuthInterceptorClient) getOrCreateToken(operation string) (serializedTo ExpirationTime: time.Now().UTC().Add(a.expirationOffset), Publisher: a.publisher, } - tokenDataBytes, err := json.Marshal(tokenData) + + tokenString, err := SignToken(&tokenData, a.sign) if err != nil { return serializedToken{}, err } - signature, err := a.sign(tokenDataBytes) - tokenDataEncoded := base64.StdEncoding.EncodeToString(tokenDataBytes) - signatureEncoded := base64.StdEncoding.EncodeToString(signature) + token = serializedToken{ expirationTime: tokenData.ExpirationTime, - bearer: fmt.Sprintf("%s.%s", tokenDataEncoded, signatureEncoded), + bearer: tokenString, } a.tokens[operation] = token } return token, nil } +func SignToken(tokenData *Token, sign SignFunc) (string, error) { + tokenDataBytes, err := json.Marshal(tokenData) + if err != nil { + return "", err + } + signature, err := sign(tokenDataBytes) + tokenDataEncoded := base64.StdEncoding.EncodeToString(tokenDataBytes) + signatureEncoded := base64.StdEncoding.EncodeToString(signature) + + tokenString := fmt.Sprintf("%s.%s", tokenDataEncoded, signatureEncoded) + + return tokenString, nil +} + func (a *AuthInterceptorClient) authorize(operation string, header http.Header) error { token, err := a.getOrCreateToken(operation) if err != nil { diff --git a/pkg/proto/protoconnect/provision-pod.connect.go b/pkg/proto/protoconnect/provision-pod.connect.go index d40c3ab5..3001ce5e 100644 --- a/pkg/proto/protoconnect/provision-pod.connect.go +++ b/pkg/proto/protoconnect/provision-pod.connect.go @@ -20,7 +20,7 @@ import ( // generated with a version of connect newer than the one compiled into your binary. You can fix the // problem by either regenerating this code with an older version of connect or updating the connect // version compiled into your binary. -const _ = connect.IsAtLeastVersion1_13_0 +const _ = connect.IsAtLeastVersion0_1_0 const ( // ProvisionPodServiceName is the fully-qualified name of the ProvisionPodService service. @@ -49,15 +49,6 @@ const ( ProvisionPodServiceGetPodLogsProcedure = "/apocryph.proto.v0.provisionPod.ProvisionPodService/GetPodLogs" ) -// These variables are the protoreflect.Descriptor objects for the RPCs defined in this package. -var ( - provisionPodServiceServiceDescriptor = proto.File_provision_pod_proto.Services().ByName("ProvisionPodService") - provisionPodServiceProvisionPodMethodDescriptor = provisionPodServiceServiceDescriptor.Methods().ByName("ProvisionPod") - provisionPodServiceUpdatePodMethodDescriptor = provisionPodServiceServiceDescriptor.Methods().ByName("UpdatePod") - provisionPodServiceDeletePodMethodDescriptor = provisionPodServiceServiceDescriptor.Methods().ByName("DeletePod") - provisionPodServiceGetPodLogsMethodDescriptor = provisionPodServiceServiceDescriptor.Methods().ByName("GetPodLogs") -) - // ProvisionPodServiceClient is a client for the apocryph.proto.v0.provisionPod.ProvisionPodService // service. type ProvisionPodServiceClient interface { @@ -81,26 +72,22 @@ func NewProvisionPodServiceClient(httpClient connect.HTTPClient, baseURL string, provisionPod: connect.NewClient[proto.ProvisionPodRequest, proto.ProvisionPodResponse]( httpClient, baseURL+ProvisionPodServiceProvisionPodProcedure, - connect.WithSchema(provisionPodServiceProvisionPodMethodDescriptor), - connect.WithClientOptions(opts...), + opts..., ), updatePod: connect.NewClient[proto.UpdatePodRequest, proto.ProvisionPodResponse]( httpClient, baseURL+ProvisionPodServiceUpdatePodProcedure, - connect.WithSchema(provisionPodServiceUpdatePodMethodDescriptor), - connect.WithClientOptions(opts...), + opts..., ), deletePod: connect.NewClient[proto.DeletePodRequest, proto.DeletePodResponse]( httpClient, baseURL+ProvisionPodServiceDeletePodProcedure, - connect.WithSchema(provisionPodServiceDeletePodMethodDescriptor), - connect.WithClientOptions(opts...), + opts..., ), getPodLogs: connect.NewClient[proto.PodLogRequest, proto.PodLogResponse]( httpClient, baseURL+ProvisionPodServiceGetPodLogsProcedure, - connect.WithSchema(provisionPodServiceGetPodLogsMethodDescriptor), - connect.WithClientOptions(opts...), + opts..., ), } } @@ -151,26 +138,22 @@ func NewProvisionPodServiceHandler(svc ProvisionPodServiceHandler, opts ...conne provisionPodServiceProvisionPodHandler := connect.NewUnaryHandler( ProvisionPodServiceProvisionPodProcedure, svc.ProvisionPod, - connect.WithSchema(provisionPodServiceProvisionPodMethodDescriptor), - connect.WithHandlerOptions(opts...), + opts..., ) provisionPodServiceUpdatePodHandler := connect.NewUnaryHandler( ProvisionPodServiceUpdatePodProcedure, svc.UpdatePod, - connect.WithSchema(provisionPodServiceUpdatePodMethodDescriptor), - connect.WithHandlerOptions(opts...), + opts..., ) provisionPodServiceDeletePodHandler := connect.NewUnaryHandler( ProvisionPodServiceDeletePodProcedure, svc.DeletePod, - connect.WithSchema(provisionPodServiceDeletePodMethodDescriptor), - connect.WithHandlerOptions(opts...), + opts..., ) provisionPodServiceGetPodLogsHandler := connect.NewServerStreamHandler( ProvisionPodServiceGetPodLogsProcedure, svc.GetPodLogs, - connect.WithSchema(provisionPodServiceGetPodLogsMethodDescriptor), - connect.WithHandlerOptions(opts...), + opts..., ) return "/apocryph.proto.v0.provisionPod.ProvisionPodService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29214887..90acbb03 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,6 +85,9 @@ importers: '@tanstack/react-query': specifier: ^5.59.19 version: 5.59.19(react@18.3.1) + apocryph-abi-ts: + specifier: workspace:^ + version: link:../../pkg/abi-ts outdent: specifier: ^0.8.0 version: 0.8.0 diff --git a/storage/frontend/package.json b/storage/frontend/package.json index 5013508b..a4278fcd 100644 --- a/storage/frontend/package.json +++ b/storage/frontend/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@tanstack/react-query": "^5.59.19", + "apocryph-abi-ts": "workspace:^", "outdent": "^0.8.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/storage/frontend/src/App.css b/storage/frontend/src/App.css new file mode 100644 index 00000000..d45902e8 --- /dev/null +++ b/storage/frontend/src/App.css @@ -0,0 +1,45 @@ +.error-toast { + position: fixed; + z-index: 10; + padding: 1em; + bottom: 1em; + right: 0.5em; + background: #553333ee; + max-width: calc(max(50vw, 500px)); +} +.error-toast:before { + content: attr(data-pop-text) '✘ '; +} + +@media (prefers-reduced-motion: no-preference) { + .error-toast { + animation: 0.6s ease error-toast-in; + } +} + +@keyframes error-toast-in { + 0% { + transform: translateX(100%); + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + transform: translateX(0%); + } +} +@keyframes action-pop-before { + 0% { + top: calc(-100% - 1.2em); + } + 30% { + top: 0%; + } + 70% { + top: 0%; + } + 100% { + top: calc(-100% - 1.2em); + } +} diff --git a/storage/frontend/src/App.tsx b/storage/frontend/src/App.tsx index 06270043..a62ce228 100644 --- a/storage/frontend/src/App.tsx +++ b/storage/frontend/src/App.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react' -import { useAccount, useConnect } from 'wagmi' +import { useAccount, useConnect, usePublicClient, useWalletClient } from 'wagmi' import { injected } from 'wagmi/connectors' import { formatUnits, parseUnits } from 'viem' import { outdent } from 'outdent' @@ -9,13 +9,17 @@ import { tomorrowNight as syntaxStyle } from 'react-syntax-highlighter/dist/esm/ import BlurUpdatedInput from './BlurUpdatedInput' import ActionPopButton from './ActionPopButton' -import apocryphLogo from '../public/apocryph.svg' -import metamaskLogo from '../public/metamask.svg' +import { watchAvailableFunds, depositFunds } from './contracts' +import apocryphLogo from '/apocryph.svg?url' +import metamaskLogo from '/metamask.svg?url' +import './App.css' const documentationLink = "https://comrade-coop.github.io/apocryph/" function App() { const account = useAccount() + const publicClient = usePublicClient() + const walletClient = useWalletClient() const { connect } = useConnect() const oneGb = parseUnits('1', 6) @@ -25,12 +29,30 @@ function App() { const decimals = 18 const priceGbSec = parseUnits('0.000004', decimals) const [ funds, setFunds ] = useState(() => BigInt(durationMultiplier) * amountGb * priceGbSec / oneGb) - const [existingDeposit, setExistingDeposit] = useState(0n) // parseUnits('1.32', decimals) + const [existingDeposit, setExistingDeposit] = useState(undefined) + const [depositInProgress, setDepositInProgress] = useState(false) + const [depositError, setDepositError] = useState('') + // TODO: const minDeposit = STORAGE_CHANNEL_RESERVATION useEffect(() => { - setInterval(() => { - setExistingDeposit(x => x > 400n ? x - 400n : x) - }, 20000) - }, []) + if (publicClient && account?.address) { + return watchAvailableFunds(publicClient, account.address, (availableFunds) => { + setExistingDeposit(availableFunds) + }) + } + }, [publicClient, account]) + + async function topUpDeposit() { + if (existingDeposit !== undefined && publicClient && walletClient?.data) { + setDepositInProgress(true) + setDepositError('') + try { + await depositFunds(publicClient, walletClient.data, funds - existingDeposit) + } catch(err) { + setDepositError(err + '') + } + setDepositInProgress(false) + } + } const duration: number = Number(funds) / Number(amountGb) / Number(priceGbSec) * Number(oneGb) @@ -89,6 +111,36 @@ function App() { }, }) ` + }, + 'Go': { + language: 'go', + code: () => outdent` + package main + + import ( + "log" + + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" + ) + + func main() { + accessKeyID := "Q3AM3UQ867SPQQA43P2F" + secretAccessKey := "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" + useSSL := true + + // Initialize minio client object. + minioClient, err := minio.New("${bucketLinkHref}", &minio.Options{ + Creds: credentials.NewCustomTokenCredentials("${bucketLinkHref}", "${stsCustomToken}", ""), + Secure: useSSL, + }) + if err != nil { + log.Fatalln(err) + } + + log.Printf("%#v\n", minioClient) // minioClient is now setup + } + ` } } const [ codeExample, setCodeExample ] = useState("JavaScript") @@ -161,26 +213,25 @@ function App() { onChange={setFunds}/> {currency} - { existingDeposit > 0n ? - : - <> - }
-
+ {depositError != '' ?
{depositError}
: <>} -
0n ? '' : 'none'}}> +
0n ? '' : 'none'}}>

Step 3: Access

-
0n && s3Token ? '' : 'none'}} className="two-columns"> +
0n && s3Token ? '' : 'none'}} className="two-columns">

Step 4: Hack

{ @@ -231,7 +282,7 @@ function App() { {codeExamples[codeExample]?.code()}
-
0n && s3Token ? '' : 'none'}}> +
0n && s3Token ? '' : 'none'}}>

Step 5: Profit