Station Smart Contract Wallet
Context
Meridian Impact Evaluator payments into Station wallets have a fundamental problem: Every transfer incurs gas fees, which would have to be paid by PL. This both causes unnecessary cost (Station operators don’t need to use their funds every IE round, so most transfers are wasted) and opens an attack vector (funds can be drained by inflating the participant count).
Proposal
This document is proposing a shift in Station wallet architecture, towards a more smart contract based model. The IE smart contract now holds participants’ balances, and Station operators withdraw on demand. PL doesn’t need to pay gas for reward each IE round any more, yet Station operators can still withdraw for free, through a new sponsored withdrawal service (which subtracts gas cost to fund itself). Through this, the intermediary Station Wallet doesn’t need to hold any balance any more, and can simply function as an identity provider for withdrawing and in general interacting with the smart contract.
Changes
Smart Contract
- add state
mapping(address => uint) balances
- add function
getBalance() public view returns uint- return state
- add function
transfer(address target, uint amount) public- support f4/0x addresses
- support f1 addresses
- update state (optional gc)
- transfer funds
- emit event
Transfer(address indexed from, address to, uint amount)
- add function
transferOnBehalf(address participant, bytes signature, address target, uint amount) public- verify signature
check(signature, from, to, amount)
- send
0.1FIL(adjustable) back to sender, to pay for gas
transfer()with differentparticipantaddress and0.1FILdeducted
- verify signature
- change function
reward(address[] addresses, uint[] scores) private- update state
Station Desktop
- update onboarding text
- update empty wallet text
| Function | Before | After |
| Show balance | this.signer.getBalance() | contract.getBalance() |
| Withdraw FIL (sponsored) | this.signer.sendTransaction(to, amount) | fetch(transferAPI, { |
| List transactions | fetch('https://filfox.info/.../${address}) | contract.filters.Transfer(signer.address)The wallet history will be changed to withdrawal history, as funds theoretically come in every 30m (or as configured), which would just bloat the list |
Station Core
- When operators supply their Station Desktop address as
FIL_WALLET_ADDRESS, they can use Station Desktop to withdraw (sponsored). This is the suggested flow.
- Operators can withdraw manually by direct contract interaction or using the unsponsored transfer web UI (coming later, see below)
Sponsored transfer service
- Bootstrap web service with wallet
- Add route
POST /transfer- read
{ from, signature, to, amount }
- verify signature
check(signature, from, to, amount)
- acquire mutex lock on
fromaddress
- call
contract.balances(from)to verifybalance > 0.1 FIL
- call
contract.transferFor(from, signature, to, amount)- contract sends
amount - 0.1FILtoto
- contract sends
0.1FILback, to pay for gas
- amount deducted can be tweaked
- contract sends
- await tx confirmed
- release mutex lock on
fromaddress
- read
Unsponsored transfer
- Participants may withdraw anytime by calling
contract.transfer(target, amount)
- The Station team might eventually provide a web UI for this, just like Saturn did.
Future
This change aligns us more closely with ERC20 token contracts, and the way of operating in an L2 IPC subnet (where you also withdraw on demand). Therefore, it both fixes a current flaw as well as aligning us better with upcoming architectural changes.
Resourcing
@Julian Gruber can work on this mostly independently, assuming code reviews from @Miroslav Bajtoš.
Timeline
Until LabWeek 2023, Nov 13th.
A very basic version of the flow can be implemented in 1d, the rest of time is spent making it stable and polished.
Known problems
- It’s possible to add unlimited participants to the
balancesmapping, as we’re sponsoring the measurement and evaluate service gas, allowing participants to introduce new addresses without paying for it. I anticipate that the requirement of performing non-fraudulent Spark work in order to get added to thebalancesmapping is enough of a hurdle to prevent unbound growth.