Contract detail
OnboardingSponsor
OnboardingSponsor is a verified Robinhood Chain smart contract at 0xa6ff0840…795d8f82 with 1 indexed transactions.
- Address type
- Smart contract
- Transactions
- 1
- First indexed block
- #112595
- Latest indexed block
- #112595
- ERC-20 transfers
- 1
- Emitted events
- 2
- Verification
- Verified
- Compiler
- v0.8.30+commit.73712a01
- Verification match
- full
- Creation transaction
- 0x47337b11…8ef03afd
- Created at block
- #112595
- Language
- solidity
- EVM version
- prague
- License
- none
- Compiler optimization
- Enabled · 200 runs
Related activity
Transactions
| Transaction | Block | From | To | Value (wei) |
|---|---|---|---|---|
| 0xdaf05404…faa225d1 | #112595 | 0x6a225f09…5d40c84d | 0xa6ff0840…795d8f82 | 0 |
Verified contract
Contract source code
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../interfaces/IMintSponsor.sol";
import "../interfaces/IV2Types.sol";
/**
* @title OnboardingSponsor
* @notice Funds a predictor's collateral during mint, gated by per-user budgets
* @dev Designed for onboarding: user enters invite code → API signer grants budget →
* user mints → escrow calls fundMint → sponsor transfers collateral
*
* Constraints enforced on-chain:
* - Required counterparty (e.g. vault-bot) — prevents self-dealing
* - Max entry price cap — prevents risk-free farming on near-certain outcomes
* Entry price = predictorCollateral / (predictorCollateral + counterpartyCollateral)
*
* Roles:
* - Owner: sweep funds, set match limit, set/rotate budget manager
* - Budget manager: set per-user budgets (intended for an API signer)
*
* Anyone can fund the contract by transferring collateral tokens to it.
* Deploy a new instance if the escrow, collateral token, counterparty, or price cap changes.
*/
contract OnboardingSponsor is IMintSponsor, Ownable {
using SafeERC20 for IERC20;
// ============ Types ============
struct Budget {
uint256 allocated;
uint256 used;
}
// ============ Events ============
event Sponsored(
address indexed predictor, uint256 collateral, address indexed escrow
);
event BudgetSet(address indexed beneficiary, uint256 allocated);
event BudgetManagerSet(address indexed manager);
event MatchLimitSet(uint256 matchLimit);
// ============ Errors ============
error UnauthorizedEscrow();
error UnauthorizedBudgetManager();
error UnauthorizedCounterparty();
error EntryPriceTooHigh();
error NoBudget();
error BudgetExceeded();
error CollateralExceedsMatchLimit();
error NativeTransferFailed();
error ArrayLengthMismatch();
// ============ Constants ============
/// @notice Basis points denominator (100% = 10000)
uint256 public constant BPS = 10_000;
// ============ State ============
/// @notice The escrow contract authorized to call fundMint
address public immutable escrow;
/// @notice The collateral token used for sponsorship
IERC20 public immutable collateralToken;
/// @notice Required counterparty for sponsored mints (e.g. vault-bot)
address public immutable requiredCounterparty;
/// @notice Maximum entry price in basis points (e.g. 7000 = 0.70)
uint256 public immutable maxEntryPriceBps;
/// @notice Maximum collateral the sponsor will fund per mint
uint256 public matchLimit;
/// @notice Address authorized to set user budgets (e.g. API signer)
address public budgetManager;
/// @notice Per-beneficiary sponsorship budgets
mapping(address => Budget) public budgets;
// ============ Constructor ============
constructor(
address escrow_,
address collateralToken_,
address requiredCounterparty_,
uint256 maxEntryPriceBps_,
uint256 matchLimit_,
address owner_
) Ownable(owner_) {
escrow = escrow_;
collateralToken = IERC20(collateralToken_);
requiredCounterparty = requiredCounterparty_;
maxEntryPriceBps = maxEntryPriceBps_;
matchLimit = matchLimit_;
}
// ============ IMintSponsor ============
/// @inheritdoc IMintSponsor
function fundMint(
address, /* escrow_ */
IV2Types.MintRequest calldata request
)
external
override
{
if (msg.sender != escrow) revert UnauthorizedEscrow();
// Enforce required counterparty (prevents self-dealing)
if (request.counterparty != requiredCounterparty) {
revert UnauthorizedCounterparty();
}
// Enforce entry price cap (prevents risk-free farming)
// entryPrice = predictorCollateral / totalCollateral
uint256 totalCollateral =
request.predictorCollateral + request.counterpartyCollateral;
uint256 entryPriceBps =
(request.predictorCollateral * BPS) / totalCollateral;
if (entryPriceBps > maxEntryPriceBps) revert EntryPriceTooHigh();
if (request.predictorCollateral > matchLimit) {
revert CollateralExceedsMatchLimit();
}
Budget storage budget = budgets[request.predictor];
if (budget.allocated == 0) revert NoBudget();
if (budget.used + request.predictorCollateral > budget.allocated) {
revert BudgetExceeded();
}
budget.used += request.predictorCollateral;
collateralToken.safeTransfer(escrow, request.predictorCollateral);
emit Sponsored(request.predictor, request.predictorCollateral, escrow);
}
// ============ View ============
/// @notice Remaining sponsorship budget for a beneficiary
function remainingBudget(address beneficiary)
external
view
returns (uint256)
{
Budget storage b = budgets[beneficiary];
return b.allocated > b.used ? b.allocated - b.used : 0;
}
// ============ Budget Manager ============
/// @notice Set a single beneficiary's budget
function setBudget(address beneficiary, uint256 allocated) external {
_checkBudgetManager();
budgets[beneficiary].allocated = allocated;
emit BudgetSet(beneficiary, allocated);
}
/// @notice Set budgets for multiple beneficiaries
function setBudgets(
address[] calldata beneficiaries,
uint256[] calldata allocations
) external {
_checkBudgetManager();
if (beneficiaries.length != allocations.length) {
revert ArrayLengthMismatch();
}
for (uint256 i = 0; i < beneficiaries.length; i++) {
budgets[beneficiaries[i]].allocated = allocations[i];
emit BudgetSet(beneficiaries[i], allocations[i]);
}
}
// ============ Owner ============
/// @notice Set the budget manager (API signer for invite codes)
function setBudgetManager(address manager) external onlyOwner {
budgetManager = manager;
emit BudgetManagerSet(manager);
}
/// @notice Set the maximum collateral per mint
function setMatchLimit(uint256 matchLimit_) external onlyOwner {
matchLimit = matchLimit_;
emit MatchLimitSet(matchLimit_);
}
/// @notice Sweep ERC20 tokens
function sweepToken(IERC20 token, address to, uint256 amount)
external
onlyOwner
{
token.safeTransfer(to, amount);
}
/// @notice Sweep native gas tokens
function sweepNative(address payable to, uint256 amount)
external
onlyOwner
{
(bool success,) = to.call{ value: amount }("");
if (!success) revert NativeTransferFailed();
}
// ============ Funding ============
/// @notice Accept native gas token deposits
receive() external payable { }
// ============ Internal ============
function _checkBudgetManager() internal view {
if (msg.sender != budgetManager && msg.sender != owner()) {
revert UnauthorizedBudgetManager();
}
}
}