On-chain actions
Check EVM contract events and Solana program transactions of a member's linked wallets.
Quests check what a member's linked wallet did on chain with two evaluator readers: ctx.wallet.events() for EVM contract events and ctx.wallet.transactions() for Solana programs. Both follow the evaluator reader rules: missing is null, a failure throws. The wallets guide installs and configures the ready-made quests that use them.
EVM events
ctx.wallet.events({ event, account, since, afterActivation?, where?, address?, newest? }) reads the events of one EVM contract whose account argument is one of the member's pinned linked EVM wallets, from since to the chain's finalized block (its safe block on a chain without a finalized tag). event is a CAIP-10 contract, a space and the event's Solidity signature with argument names and indexed markers, such as "eip155:8453:0x… Transfer(address indexed from, address indexed to, uint256 value)" (a leading event keyword is accepted): every argument named, at most three indexed, at least one indexed address, no tuples or arrays of arrays. Take it from a settings.contractEvent({ label, description?, default }) setting, typed ContractEvent; never from member input. isContractEvent() and parseContractEvent() from @domino-sdk/relay/authoring check the syntax. account names the indexed address argument matched against the wallets, such as "to". since is epoch milliseconds, at most 30 days before the evaluation (maxWalletEventLookback); afterActivation: true raises it to the quest's first activation in the environment.
It returns { event, chain, contract, since, at, finality, complete, logs, observedAt }: event with the contract EIP-55 checksummed, at the newest block read ({ kind: "block", number }), finality "finalized" or "safe", complete whether logs holds every matching event since since (absent from results captured before Relay reported it), and logs oldest first, each { wallet, block, timestamp, tx, logIndex, args } with timestamp the block's time in epoch milliseconds and args every argument by name (integers as decimal strings, addresses EIP-55, bytes lowercase hex, an array argument as an array). Key a repeatable completion on `${chain}:${tx}:${logIndex}`.
wheremaps argument names to an equality (an address in any case, a decimal integer,true/false, or hex bytes) or{ gte: "<decimal>" }for an integer argument; every condition must hold, at most 10. Equalities on indexed static arguments also narrow the RPC topic filter. A condition on theaccountargument, an unknown argument, an array, an indexed dynamic argument (stored as its hash) or a value of the wrong type throwsauthor-error.- Only events that name the member in an indexed argument can be read: a router or aggregator flow where the member is only
tx.fromcannot. No Cosmos or Snapshot activity; Solana programs are read withctx.wallet.transactions(). - Reads go through dRPC:
eth_chainId, the finalized (or safe) block, the first block estimated from block times and checked against its header, theneth_getLogsover 10,000-block ranges, several requests at a time, a refused range halved down to 100 blocks (below that,provider-unavailable). A read over more than 3,000,000 blocks or more than 1,000 logs (limits.walletEventLogs, counted beforewherefilters non-indexed arguments) throwsauthor-error. 3,000,000 blocks is under 30 days on fast chains; at block times measured in October 2026, about: Robinhood 3 days, Gravity 8, opBNB 8, Arbitrum One 9, Monad 10, Cronos 14, Plume 15, BNB Smart Chain 15, Sei 16, Telos 17, Arc 17, Tempo 19, Playnance 20, Aurora 22, Stable 24, Moca 28. Only finalized blocks are read, so no reorg handling is needed; recent events appear once final (about 13 minutes on Ethereum, longer on rollups). newest: n(1 to 1,000) returns at most thennewest matching events: Relay scans back from the head and stops at either cap instead of throwing, withcomplete: falsewhen it left older matching events out or stopped beforesince. Withoutnewest, a read returns every matching event withcomplete: trueor throws. Use it for a busy wallet or a long window on a fast chain.- Publication, Console settings edits and unarchiving check every
settings.contractEvent()value: the syntax, a chain Relay reads, a chain that reports afinalizedorsafeblock, and code at the address. Refusals are422 validation-failednaming the setting's label; an RPC outage is503 unavailable; a platform without a dRPC key refuses every event with503 not-configured. address,nulland failures followctx.wallet.balance(): no linked EVM wallet returnsnull; outages throwprovider-unavailable. Reads are captured, replayed on retries, and count toward the eight capability calls.WalletReader.eventsis optional in the type so that older test doubles compile; hosted evaluations always supply it, so call it asctx.wallet.events!(…). The testing kit answers it fromfacts.wallet.events(called with{ event, account, since, afterActivation, where, address }, defaultsfalseandnull, plusnewestwhen the quest sets it) without applyingsince,whereornewest, null whilefacts.wallet.listlinks no EVM wallet; a run'scapabilityCallslists event reads aswallet-events.- Anti-sybil: set a minimum with
whereandgte, count only logs at or after the wallet'sverifiedAt, keepsincerecent, and add a member budget.
Snippet onchain-action installs a complete quest: a settings.contractEvent() event, the participant argument, an optional settings.amount() minimum, events since the later of activation and the wallet's verifiedAt within 30 days read with newest: maxWalletEventLogs, and a keyed completion on chain:tx:logIndex that skips events already rewarded.
Solana transactions
ctx.wallet.transactions({ program, since, afterActivation?, mint?, address? }) reads the successful transactions at finalized commitment of the member's pinned linked Solana wallets since since that invoked program in a top-level or inner instruction (lookup-table addresses resolved), oldest first. Take program from a settings.solanaProgram({ label, description?, default }) setting (base58, isSolanaProgram()); never from member input. since is epoch milliseconds, at most 30 days before the evaluation (maxWalletTransactionLookback); afterActivation: true raises it to the quest's first activation. With mint (an SPL mint), Relay pages the signatures of each wallet's associated token account for the mint instead of the wallet's: far fewer transactions, but tokens in other token accounts are not seen.
It returns { program, chain, mint, since, at, complete, transactions, observedAt }: at the finalized slot read ({ kind: "slot", number }), and transactions each { wallet, signature, slot, timestamp, signer, programs, balanceChanges }, with signer whether that wallet signed it (read from the parsed account keys; false in results captured before it existed and in testing-kit facts that omit it), programs every invoked program in call order (each top-level instruction's program, then the programs it called) and balanceChanges [{ mint, delta }] (mint "SOL" for lamports, rent included and the fee left out when the wallet paid it, wrapped SOL under its own mint; delta a signed integer string; unchanged mints omitted). Token changes come from preTokenBalances/postTokenBalances of accounts the wallet owns, so tokens routed through accounts it does not own do not show. A transaction touching two linked wallets appears once per wallet. A transaction appears when it touched the wallet (or, with mint, its token account), whoever signed it: an incoming transfer from someone else's swap counts as received, with signer false. Check signer for anything that rewards an action. Key a repeatable completion on signature.
- Helpers in
@domino-sdk/relay/authoring, pure over one transaction:received(tx, mint)andspent(tx, mint)(integer string or"0"),swapped(tx, { into?, from? })(one mint down, a different one up),invoked(tx, program),instruction(tx, name)(first decoded instruction's args, ornull). - Instructions: saving a
settings.solanaProgram()value (publication, Console edit) stores the program's Anchor IDL (legacy or 0.30+) from its on-chain IDL account in the release'ssolanaIdls[settingKey]({ program, source: "chain" | "pasted", idl }); a later on-chain upgrade changes nothing until the next save. For a program without one, an operator pastes it in Console (quest → Instruction decoding) or sendssolanaIdls: { [settingKey]: idl | null }withPOST /releases/settings; a pasted IDL that is not Anchor, names anotheraddress, or exceeds 512 KiB is422; an on-chain IDL wins at the next save. With an IDL, each transaction hasinstructions: the program's top-level and inner instructions in call order,{ name, args, accounts }, matched on the first 8 bytes (the IDL'sdiscriminator, elsesha256("global:<snake_case name>")[..8]); undescribed or undecodable ones are skipped. Names as the IDL spells them. Args: ints up to 32 bits and floats as numbers, 64/128-bit as decimal strings,pubkeybase58,bytes/vec/arrays as arrays, absent optionnull, struct as object, enum{ Variant: fields }.accountsmaps IDL account names (compositegroup.name) to addresses. Without an IDL,instructionsis absent. - Each wallet's read fetches at most 1,000 successful transactions (
limits.walletTransactions), newest first; a wallet with more, or whose newest 10,000 signatures don't reachsince, returns its newest ones withcomplete: falseand does not throw. - Reads use the platform's Solana RPC (dRPC where it has the key):
getSignaturesForAddressatfinalized, failed signatures skipped, thengetTransactionwithjsonParsedandmaxSupportedTransactionVersion: 1. A transaction appears about 13 seconds after it lands. - Publication, Console settings edits and unarchiving check every
settings.solanaProgram()value: an address with no account or a non-executable account is422 validation-failednaming the setting's label; an RPC outage is503 unavailable. address,nulland failures followctx.wallet.balance(): no linked Solana wallet returnsnull; outages throwprovider-unavailable. Reads are captured, replayed on retries, and count toward the eight capability calls.WalletReader.transactionsis optional in the type so that older test doubles compile; hosted evaluations always supply it, so call it asctx.wallet.transactions!(…). The testing kit answers it fromfacts.wallet.transactions(called with{ program, since, afterActivation, mint, address }, defaultsfalseandnull; transactions may carryinstructions) without applyingprogram,sinceormint, null whilefacts.wallet.listlinks no Solana wallet; a run'scapabilityCallslists transaction reads aswallet-transactions.- Anti-sybil: someone else can send tokens to a member's wallet in a transaction that calls the program, so a
received()check alone rewards transfers; requiretx.signer, a minimum withreceived()against asettings.amount(), count only transactions at or after the wallet'sverifiedAt, keepsincerecent, and add a member budget.
Snippet solana-action installs a complete quest: a settings.solanaProgram() program, an optional token mint (an SPL mint, which narrows the read to its associated token accounts, or "SOL") with a settings.amount() minimum received, transactions the linked wallet signed since the later of activation and its verifiedAt within 30 days, and a keyed completion on signature that skips transactions already rewarded. Its README shows a spent() variant for deposits and staking.