Endpoints
The contract exposes 55 state-changing endpoints — 30 restricted to the owner, 25 callable by anyone with the right to do so. Read-only calls are on Views.
Contract crate corelayer 1.0.0, built with
multiversx-sc 0.66.2 on rustc 1.94.1 (e408947bf 2026-03-25).
The ABI itself is served at /abi/corelayer.abi.json.
Deploy
Deployed PAUSED. No sync call happens here: the deep venue validation is setSwapVenue, which the launch script must run before the first deposit (venue_validated gates deposits). Arguments come from deploy/networks.json and from nowhere else (D-018).
| Argument | Type | |
|---|---|---|
chain_id | bytes | |
usdc_token_id | TokenIdentifier | |
wegld_token_id | TokenIdentifier | |
pair_address | Address | |
wrapper_address | Address | |
initial_tariff | u64 | |
ru_schedule_hash | array32<u8> |
Upgrade
No arguments, ever. Requires ALL FOUR money scopes paused (K-19): paused alone does not stop renew*, settleUsage or try_distribute, which would otherwise run on unverified code from the first block after the upgrade. The contract STAYS paused in all four scopes; the owner unpauses scope by scope after the post-upgrade verification. The owner check is written out rather than declared with #[only_owner]: the derive macro applies that attribute to #[endpoint] methods only and drops it silently on #[upgrade]. It is defence in depth (SPEC-DEVIATIONS DEV-03) — the protocol already refuses an upgradeContract transaction from anyone but the contract owner — but INV-K7 names upgrade as owner-only, and a guard that lives outside the code cannot be asserted by a test.
No arguments.
Open endpoints
Anyone may call these; the contract checks the caller’s right inside. payable endpoints take the payment token in the call itself.
setReporter
Owner: immediate. Operator: effective after REPORTER_CHANGE_DELAY_MS (K-20); the operator's instant incident tool is pauseSettlement().
| Argument | Type | |
|---|---|---|
reporter | Address |
pause
No arguments.
pauseDeposits
No arguments.
pauseSettlement
No arguments.
pauseDistribution
No arguments.
pauseRenewals
No arguments.
subscribe
| Argument | Type | |
|---|---|---|
tier_id | u32 | |
months | u8 | |
max_price | u64 | |
reference | optional<bytes> | variadic |
renew
Permissionless, idempotent, never paused, never reverts for a business reason. An unknown account answers NoAccount instead of reverting.
| Argument | Type | |
|---|---|---|
account | Address |
| Returns | Type | |
|---|---|---|
— | RenewOutcome |
renewMany
The same, per address, independently. An EMPTY batch is a no-op rather than a revert: this endpoint is relayed at our own cost. Totals is read ONCE before the loop and written at most ONCE after it, the §4.1 cache pattern settleUsage already follows. Reading and writing the one global key per account cost a 50-account batch up to 50 loads and 50 stores (~6.6 M gas at the 02 §6 schedule) that the §13.2 budget does not carry, and renewMany is an INTERNAL free-relay call whose declared gasLimit C-13 caps at 1.5 × the measured figure.
| Argument | Type | |
|---|---|---|
accounts | variadic<Address> | variadic |
| Returns | Type | |
|---|---|---|
— | variadic<RenewOutcome> | variadic |
setAutoRenew
Sets the flag and the two guards; it does NOT renew (S §4). Zero never means "unlimited": an autonomous buyer must state its ceiling.
| Argument | Type | |
|---|---|---|
enabled | bool | |
renew_tier_id | u32 | |
max_renew_price | u64 |
setPayg
Four arguments, exactly (K-04): a client that encodes three gets an argument-decode revert that OUR relayer pays for on the free flow. enabled = false never releases escrow — it only starts the close. Disabling twice keeps the FIRST payg_disabled_at_ms, so the 1 h and 7 d clocks cannot be restarted by spamming the free call. Split gate (SPEC-DEVIATIONS DEV-17): turning PAYG ON is subject to paused (Gate::All), turning it OFF is not (Gate::Open). setPayg is the ONLY way to raise ACC_FLAG_PAYG_CLOSING and start the 1 h / 7 d clocks, and releaseEscrow is deliberately never paused so that "a dead or hostile reporter can never lock user funds". With one Gate::All for both directions the operator's warm pause() key froze every payg_escrow in the system indefinitely while settleUsage (Gate::Settlement) kept debiting it. Closing takes no fee, needs no venue and only ever moves the account towards release, so it cannot be used to bypass a pause.
| Argument | Type | |
|---|---|---|
enabled | bool | |
budget | u64 | |
auto_topup | bool | |
max_payg_price | u64 |
releaseEscrow
The fallback release (S §5.3 case 2): permissionless, never paused, and it can only move the account's OWN escrow into its OWN credits, once per PAYG close. The normal path is the reporter's closing line. An address with no record answers ERR_RELEASE_NOT_DUE, not ERR_NO_ACCOUNT: §12.3 gives this endpoint exactly two preconditions and §12.1's "the record must exist" rule is for endpoints whose CALLER is the account. A permissionless sweeper walking a list of addresses must read "nothing to release" for an unknown one, not a billing failure. The lazy block roll runs first, exactly as setPayg does, so the period_id of the emitted escrowMoved is the period now really falls in and not the last id of a block that already ended.
| Argument | Type | |
|---|---|---|
account | Address |
settleUsage
| Argument | Type | |
|---|---|---|
batch_id | u64 | |
window_end_ms | u64 | |
usage_root | array32<u8> | |
totals | BatchTotals | |
lines | bytes |
setSettleCaps
Operator or owner. K-20: the operator may only LOWER the window cap — a stolen warm key cannot turn the 50 USDC/h reporter bound into 1,000.
| Argument | Type | |
|---|---|---|
max_settle_per_window | u64 | |
sender_fee_ru | u32 |
addSenders
| Argument | Type | |
|---|---|---|
max_fee | u64 | |
senders | variadic<Address> | variadic |
removeSenders
Fee-less, and it never refunds: the slot was paid for when it was taken. Never blocked by paused (Gate::Open, SPEC-DEVIATIONS DEV-17). This is "the payer's only defence against a compromised sender key", and isAuthorizedSender — which the relay backend reads to decide whether to keep serving that key — is a view and is never paused either. Leaving it under Gate::All meant the operator's warm pause() key kept a stolen sender key authorised and burning the customer's quota until the cold owner key could unpause. It takes no fee and only ever REMOVES state, so it cannot be used to bypass a pause.
| Argument | Type | |
|---|---|---|
senders | variadic<Address> | variadic |
activateRelayers
Registered -> Active and Draining -> Active (false alarm or planned maintenance). Pushes start with the very next distribution.
| Argument | Type | |
|---|---|---|
entries | variadic<multi<Address,u32>> | variadic |
drainRelayers
Active -> Draining: weight 0 and out of activeSet in the SAME transaction, so the very next payment no longer pays it. Deliberately unguarded — no share bound, no last-in-shard check — because an incident must never be blocked by a bound.
| Argument | Type | |
|---|---|---|
addresses | variadic<Address> | variadic |
retireRelayers
Draining -> Retired after MIN_DRAIN_MS, or Registered -> Retired immediately (a spare whose cold key is lost or suspect). Terminal, and the tombstone is permanent.
| Argument | Type | |
|---|---|---|
addresses | variadic<Address> | variadic |
setRelayerWeights
Active rows only. One relayerWeightsChanged event per CALL, never one per relayer, and one activeSet store per call.
| Argument | Type | |
|---|---|---|
entries | variadic<multi<Address,u32>> | variadic |
distributeRelayerPool
Permissionless ("no keeper" purity): anyone may flush once the gates pass. A call by the owner or the operator is FORCED and bypasses the gates — used after a weight change, after draining a compromised relayer, or to empty the pool before an upgrade. Never reverts when the gates are closed; it returns false.
No arguments.
| Returns | Type | |
|---|---|---|
— | bool |
setDistributionParams
Operator or owner. The bounds exist so that a stolen operator key can delay pushes by at most 7 days / 100 EGLD and never stop them: the owner, or any caller once the gates pass, can still flush.
| Argument | Type | |
|---|---|---|
min_interval_ms | u64 | |
min_amount | BigUint |
deposit
payable (any token)
Credits the caller's own account. Callable by anyone, smart contracts included (C-11).
No arguments.
depositFor
payable (any token)
Credits somebody else's account. The payer gets NO rights on the beneficiary's account: it is a payment, not a delegation. The beneficiary guard is evaluated INSIDE run_deposit, as §8.2 step 7, so the step-1 re-entrancy check and the step-2 pause / venue checks are answered first: a re-entrant or paused call must say ERR_REENTRANCY / ERR_PAUSED, never an argument error that looks like client noise.
| Argument | Type | |
|---|---|---|
beneficiary | Address |
depositAndSubscribe
payable (any token)
Deposit, then EXACTLY the subscribe logic of §12.2 on the caller's own account — never try_renew (K-17). A failing price, credit, tier or queue check reverts the whole call BEFORE the swap, so a doomed purchase burns little gas of our relayer and the payer keeps the USDC.
| Argument | Type | |
|---|---|---|
tier_id | u32 | |
months | u8 | |
max_price | u64 | |
reference | optional<bytes> | variadic |
Owner-only endpoints
These revert for every caller but the owner (the deployer wallet — there is no multisig).
setOperator
owner only
Owner, immediate. Clears ANY pending reporter, due or not: a rotated-out operator leaves nothing behind, and a reporter scheduled by a stolen operator key is dropped even if the owner reacts after the delay.
| Argument | Type | |
|---|---|---|
operator | Address |
setTreasury
owner only
Owner. EVERY change, including the first, waits ROLE_CHANGE_NOTICE_MS; one pending slot, last call wins and restarts the notice.
| Argument | Type | |
|---|---|---|
treasury | Address |
cancelPendingTreasury
owner only
Owner. A DUE pending treasury is already in force, so it is materialised first and can no longer be cancelled; cancelling it would be an instant treasury change that bypasses the notice.
No arguments.
setRelayerReserve
owner only
Owner. Published pointer only — the contract never transfers to it; the signer takes its only non-relayer destination from here.
| Argument | Type | |
|---|---|---|
reserve | Address |
unpause
owner only
No arguments.
unpauseDeposits
owner only
No arguments.
unpauseSettlement
owner only
No arguments.
unpauseDistribution
owner only
No arguments.
unpauseRenewals
owner only
No arguments.
setTariff
owner only
| Argument | Type | |
|---|---|---|
new_tariff | u64 |
cancelPendingTariff
owner only
A DUE pending value is materialised first and can no longer be cancelled.
No arguments.
setSwapVenue
owner only
Deep validation by read-only sync calls, reduced to what a plain swap needs: the pair trades exactly {usdc, wegld} and the wrapper unwraps exactly wegld. The pair need NOT be Active (it may be set during a pause). Requires paused || depositsPaused, so a venue switch is always followed by a canary. Token decimals cannot be read on-chain; the deploy script asserts decimals == 6.
| Argument | Type | |
|---|---|---|
pair | Address | |
wrapper | Address | |
usdc | TokenIdentifier | |
wegld | TokenIdentifier |
setMinDeposit
owner only
| Argument | Type | |
|---|---|---|
value | u64 |
setMaxDeposit
owner only
0 = unlimited (D-105). Resets the swap window cells, so a new limit starts with a fresh window.
| Argument | Type | |
|---|---|---|
value | u64 |
setRateClass
owner only
Affects only plan blocks written afterwards: blocks snapshot the numbers.
| Argument | Type | |
|---|---|---|
class | u8 | |
max_ru_per_s | u32 | |
burst_ru | u32 |
setRuSchedule
owner only
ALWAYS effective after RU_SCHEDULE_NOTICE_MS; one pending slot, last call wins and restarts the notice.
| Argument | Type | |
|---|---|---|
version | u32 | |
hash | array32<u8> |
cancelPendingRuSchedule
owner only
A DUE pending schedule is materialised first and can no longer be cancelled.
No arguments.
setTier
owner only
Creates or overwrites a DRAFT. version, status and created_ms supplied by the caller are overwritten.
| Argument | Type | |
|---|---|---|
tier | TierV1 |
activateTier
owner only
Draft -> Active. Invariants are re-checked; the rate class must exist because every plan block snapshots its numbers (K-09).
| Argument | Type | |
|---|---|---|
tier_id | u32 |
setTierStatus
owner only
| Argument | Type | |
|---|---|---|
tier_id | u32 | |
status | TierStatus | |
successor_tier_id | u32 |
addCustomBuyer
owner only
| Argument | Type | |
|---|---|---|
tier_id | u32 | |
address | Address |
removeCustomBuyer
owner only
| Argument | Type | |
|---|---|---|
tier_id | u32 | |
address | Address |
setSettleCeiling
owner only
Owner only: the hard ceiling the operator can never reach past.
| Argument | Type | |
|---|---|---|
ceiling | u64 |
grantCredits
owner only
| Argument | Type | |
|---|---|---|
account | Address | |
amount | u64 | |
reason | GrantReason |
grantRu
owner only
Creates the account when it is missing, so a trial can be granted to an address that has never deposited. Grants are independent of plan blocks and of credits: they are RU, consumed by the backend before cap and PAYG, and they vanish at expiry.
| Argument | Type | |
|---|---|---|
account | Address | |
ru | u64 | |
duration_ms | u64 | |
reason | GrantReason |
addRelayers
owner only
Registers cold rows with weight 0. operator_id and sla_class are the forward hooks of 08 §6.2; v1 accepts only OPERATOR_ID_CORELAYER. The shard is COMPUTED here, never supplied, and smart-contract addresses are refused — a wallet can always receive EGLD, which removes the only path on which a push could bounce.
| Argument | Type | |
|---|---|---|
operator_id | u32 | |
sla_class | u8 | |
addresses | variadic<Address> | variadic |
removeRelayers
owner only
Frees registry capacity 30 days after retirement. The tombstone and the events remain, so the address stays permanently unregisterable.
| Argument | Type | |
|---|---|---|
addresses | variadic<Address> | variadic |
setRelayerMeta
owner only
Forward hook only (08 §6.2): neither field is read by v1 logic beyond the operator_id == 1 rule.
| Argument | Type | |
|---|---|---|
address | Address | |
operator_id | u32 | |
sla_class | u8 |
setMaxRelayerShareBps
owner only
Owner only. Documented footgun (distribution.md §6.2): a value below 10_000 / n_active makes every activate and re-weight call revert with ERR_SHARE_BOUND until it is raised again. Draining always keeps working, so the fleet can still be stopped during an incident. The change is announced with maxRelayerShareBpsChanged and the value is also readable through getDistributionState (SPEC-DEVIATIONS DEV-21): the owner's off-chain transaction builder recomputes the bound off-chain before the owner signs an activate or a re-weight, and a value that only lives in a raw storage key is invisible to an event-driven mirror.
| Argument | Type | |
|---|---|---|
bps | u32 |
emergencyWithdrawPool
owner only
Owner only, only while the WHOLE contract is paused AND distribution is paused, destination is the treasury and nothing else. In launch mode the pool holds only atto-dust, so this surface is ≈ 0; it becomes relevant only if batching is ever switched on. Disclosed in the docs. Why distributionPaused is required too (SPEC-DEVIATIONS DEV-16): distributeRelayerPool is permissionless and runs under Gate::Open, so paused alone does not stop it. With the launch parameters (min_interval_ms = 0, min_amount = 0) a watcher of the mempool could front-run the rescue with a full permissionless push and make it revert on pool > 0. Requiring distributionPaused means the rescue is only ever reachable from a state in which try_distribute is already a no-op, so the two can never race.
No arguments.