User wallets
Every user has a single canonical UserWallet: the account that holds and
forwards funds for the tokenised asset platform’s subscriptions and
redemptions. The manager never touches platform funds directly; adapters
execute platform calls through the user’s wallet, keeping platform
interactions isolated per user. The contract lives at
src/common/UserWallet.sol, its factory at
src/common/UserWalletFactory.sol.
Deployment
Wallets are minimal-proxy clones of an immutable implementation, deployed
through the wallet factory with keccak256(abi.encode(user)) as the CREATE2
salt, so each user has exactly one wallet at an address predictable before
deployment:
function createWallet(address user) external returns (address wallet);
function getWallet(address user) external view returns (address wallet);
function predictWalletAddress(address user) external view returns (address wallet);createWallet is permissionless: anyone can deploy the wallet for any user,
and the result is the same canonical address either way. getWallet returns
the zero address until the wallet has code, which makes it the preflight
check: a wind or unwind request for a user without a wallet reverts with
WindManager__UserWalletMissing or its unwind counterpart. Integrators
should check getWallet(user) != address(0) before submitting rather than
catching the revert.
Execution through batchCall
function batchCall(
address user,
address manager,
BatchCall.Call[] calldata calls,
bool revertOnFail
) external payable returns (bytes[] memory results, bool[] memory successes);batchCall is the only way funds move out of the wallet in normal
operation, and it is gated by a three-way check: the passed user must be the
wallet’s owner (UserWallet__InvalidOwner), the passed manager must be
active in one of the manager factories the wallet factory was configured
with (UserWallet__InvalidManager), and the caller must be that manager’s
bound adapter (UserWallet__InvalidAdapter). No other party, the owner
included, can execute arbitrary calls through the wallet.
Owner transfers under daily limits
function transferWhitelistedToken(address token, address recipient, uint256 amount) external;
function getDailyTransferUsage(address token, uint256 day) external view returns (uint256 usage);The owner can make small transfers of whitelisted tokens under a per-token
daily limit. The whitelist and limits live on the factory
(setTokenTransferLimit, factory owner only; a zero limit means not
whitelisted). This exists for exchange wallet verification, where a transfer
must originate from the wallet address, and it is bounded so it can never
sweep proceeds: an ERC-20 amount is pulled from the owner and forwarded in
the same call, so it never spends what the wallet transiently holds, while
ETH (address(0)) is sent from the wallet’s own balance, which is why the
wallet accepts plain ETH. Usage accrues in day buckets
(block.timestamp / 1 days); UserWallet__DailyLimitExceeded rejects
anything past the limit.
Two whitelists apply to a wallet and they are unrelated. This one names the tokens the owner may transfer out under a daily limit, and the factory owner maintains it. A permissioned asset carries its own whitelist of addresses allowed to hold or move the token, maintained by the issuer; see how it works.
Events
UserWalletCreated and TokenTransferLimitSet on the factory,
UserWalletInitialized on the clone, and WhitelistedTokenTransferred for
owner transfers.