Source: packages/oracle-runtime-workers/src/plugins/ixo-transaction/ and the runtime-neutral packages/ixo-transaction/ (@ixo/ixo-transaction)
| Attribute | Value |
|---|---|
| Feature key | ixo-transaction |
| Runtime | Cloudflare Workers runtime only. Exported and opt-in from @ixo/oracle-runtime-workers 0.15.0; in BUNDLED_WORKERS_PLUGINS from 0.44.0. This page describes 0.44.0. |
| Visibility | on-demand, and its tools exist on Portal turns only |
| Stability | experimental |
| Category | integration |
| Default state | On (part of the Workers bundle from 0.44.0) |
| Depends on | — (the Portal must answer the sign_transaction action, below) |
Summary
The ixo-transaction plugin lets the model turn a conversation into an IXO chain transaction and ask the user's Portal wallet to sign it. The oracle never signs, broadcasts or holds a key: it validates the message against the @ixo/ixo-transaction catalog, the Portal validates it again and shows it, and the user signs and broadcasts it in their own wallet.
From 0.44.0 the plugin is part of BUNDLED_WORKERS_PLUGINS, so every oracle that spreads the bundle has it. An oracle that added new IxoTransactionPlugin() itself can drop it: a plugin listed twice by name is loaded once, as its last instance.
Portal turns only. The wallet is reached through the Portal tab's sign_transaction action, which a Matrix, Slack, WhatsApp or other channel chat does not have. On those turns the plugin offers no tools and is not in the capability catalogue; chain steps there still go through Auth Hub. A Portal turn without a connected browser tab still gets the tools, and each signing tool answers unavailable without sending anything.
Bundling it is safe because nothing is signed without the user: mainnet is refused unless IXO_TRANSACTION_ALLOW_MAINNET=true, every signing call needs the user's acceptance of each risk quoted word for word, and the user signs in their own wallet.
Environment variables
| Var | Required | Description |
|---|---|---|
IXO_TRANSACTION_ALLOW_MAINNET | no | 'false' (default) or 'true'. Off: every mainnet request is refused and nothing is sent. On: a catalog draft goes to the wallet only with the receipt of the same message signed on testnet (see Mainnet); the DAO DAO tools work on a mainnet oracle without a receipt. |
IXO_TRANSACTION_CHAIN_ID_DEVNET | no | Default devnet-1. The chain id a devnet request names. |
IXO_TRANSACTION_CHAIN_ID_TESTNET | no | Default pandora-8. The chain id a testnet request names. |
IXO_TRANSACTION_CHAIN_ID_MAINNET | no | Default ixo-5. The chain id a mainnet request names. |
IXO_TRANSACTION_SIGN_TIMEOUT_MS | no | Default 120000, at most 300000. How long the signing tools wait for the wallet. A value outside the range fails boot validation. |
NETWORK | — | Read but not owned (core). The network of the DAO DAO tools, and of the chain reads they make. |
CHAIN_REST_URL | — | Read but not owned (ixo-chain). The chain REST endpoint the DAO DAO tools read, the same one chain_lookup reads. |
The POD Creator declares the same three chain id variables, so one set configures both plugins. There is no key, mnemonic or signing endpoint to configure.
What it contributes
- Tools (Portal turns only):
| Tool | Effect | What it does |
|---|---|---|
list_ixo_transaction_routes | read | The catalog routes (/ixo {module} {action}), their fields, risk level and risks. |
classify_ixo_transaction_intent | read | Resolves a slash command, a type URL or plain words ("retire credits") to one route, or refuses with the candidates. |
validate_ixo_transaction_draft | read | Validates a draft and returns the message, its risks and risk level. Sends nothing. |
sign_ixo_transaction | write | Sends one validated message to the user's wallet. |
create_dao_group | write | Creates a DAO DAO group of its own. |
link_dao_group | write | Makes an existing DAO DAO group govern a POD or other entity the user can change. |
create_dao_proposal | write | Proposes to a DAO DAO group. |
vote_on_dao_proposal | write | Votes yes, no or abstain on a proposal. |
execute_dao_proposal | write | Carries out a passed proposal. |
close_dao_proposal | write | Closes a rejected proposal. |
- Sub-agents: none.
- Middleware: none.
- HTTP routes: none.
- Shared state: per user and conversation, in the user's own store: the previews awaiting the user's reply and the groups and links already sent (1 hour idle). Testnet receipts in the user's blob store (24 hours).
The catalog covers the entity, iid, claims, token, smart-account and daodao modules. A request for bonds, liquid staking or names is refused with the reason.
How signing works
- Disclose. The model prepares the step and shows the user what it does and every risk. The DAO DAO tools do this themselves: their first call (without
riskConfirmation) sends nothing and returns a preview with asummaryandrisks. - Confirm in a later message. The call that signs must quote every risk word for word in
riskConfirmation.acceptedRisks;confirmed: truealone, or a paraphrase, is refused. For the DAO DAO tools the signing call must also come from a later user message than the preview, with the same fields: one model turn cannot preview and sign on its own. - Sign in the wallet. The Portal validates the message against the catalog again, refuses any chain other than its wallet's, and asks the user to sign.
| Outcome | When |
|---|---|
signed | The wallet signed and the transaction succeeded: transactionHash, code, height when the wallet gave them. |
failed | Included in a block with a non-zero code; carries the hash and the chain's log. |
rejected | The user declined in the wallet. |
error | Any other refusal, for example the chain refusing the wallet's simulation. The chain's or contract's own reason reaches the model (one line, at most 600 characters), so the model can explain it; a chain error is never read as the user declining. |
timeout | No answer in time, or the Portal tab went away. The outcome is unknown: the request is never sent again on its own, and the model asks the user to check. |
validation_error | The draft, the risk acceptance or the mainnet gate failed. Nothing was sent. |
unavailable | No Portal session, no browser connected, or no signing handler in the Portal. Nothing was sent. |
DAO DAO groups
The DAO DAO tools build the contract messages themselves from a few fields, so the model never writes a nested contract message or a code id. Each is one wallet signature, previewed first and confirmed in a later message, on the oracle's NETWORK.
Create a group
create_dao_group creates the same multisig group the Portal creates: members with one vote each, a number of yes votes that passes a proposal, and the network's DAO DAO contracts. Asking for "a DAO DAO group with members X and Y, threshold 1, 1-hour voting, no vote changes" is enough.
| Input | Bounds |
|---|---|
name | 1 to 64 characters, one line |
members | 1 to 12 ixo1… account addresses, each once. The user's own account must be one of them: a group its creator is not in is refused, since they could not propose or vote in it. |
threshold | 1 to the number of members: the yes votes that pass a proposal |
votingPeriodSeconds | 1 hour to 30 days; default 7 days |
allowRevoting | Whether members may change their votes; default yes |
description | Optional, at most 300 characters |
When the user did not say how long voting lasts or whether votes can be changed, the model asks, with the trade-off: with vote changes allowed a proposal passes only when voting closes, even once it has enough yes votes; without them it passes as soon as enough members vote yes, and a vote cannot be changed.
The preview shows the exact group: its name, every member, the threshold and both voting rules. Once signed, the plugin reads the transaction back and returns the new group's coreAddress, its proposal module and its intake (where new proposals are sent), ready for the proposal tools. The same group is created once per conversation: asking again is refused while it is being signed and after it was created. When the wallet's answer was lost, the same group is held back for an hour: the model checks chain_lookup daos, or reads the group back from the transaction hash the user gives, which sends nothing.
For the governance group of a POD created in the same conversation, the model uses the POD Creator's create_pod_group instead, which also links the group to the POD.
Link a group to a POD or entity
link_dao_group({ entityDid, coreAddress }) makes an existing DAO DAO group govern a POD (or any other entity) after the fact: a POD made earlier, elsewhere or in the Portal, or a group just made with create_dao_group. "Make my existing POD a DAO" is the two in turn. One wallet signature sends what the Portal sends for a POD's group: the group linked as the entity's governing group, and added as a controller of its identity, which gives the group full control of the entity.
Before the preview, and again before signing, the plugin reads the chain:
- The user's account must be listed for authentication on the entity's identity document; the chain refuses anyone else.
- The address must be a DAO DAO group's core contract, not another contract or one of the group's modules.
- An entity another group already governs is refused: replacing a group is a separate change, made in the Portal. An entity this group already governs is refused too, and after a partial link only the missing half is sent.
The preview shows the group's name, its members and how its proposals pass. It notes when the user is not a member (allowed: a board need not include the POD's owner, but the user then has no say in its decisions) and when the entity is not a type the Portal shows a Decisions tab for. The same link is sent once per conversation. Once signed, the plugin reads the transaction back and answers linked, with the entity's Portal Decisions link, only when the link went through.
Proposals
create_dao_proposal, vote_on_dao_proposal, execute_dao_proposal and close_dao_proposal act on a group such as a POD's governance group. The model finds the addresses with chain_lookup: a POD's group from entity (governingGroup), the user's groups from daos (each proposal list's proposalModule and intake), and proposals with their tally and the user's vote from dao_proposals.
| Tool | Input |
|---|---|
create_dao_proposal | intake (or proposalModule for a list without one), title, description, actions (optional, at most 4 catalog messages the group's own account would send) |
vote_on_dao_proposal | proposalModule, proposalId, vote (yes, no or abstain), rationale (optional) |
execute_dao_proposal | proposalModule, proposalId |
close_dao_proposal | proposalModule, proposalId |
- A proposal discloses what it would do. The summary names each action with its values (for example the recipient and amount of a payment from the group), and the risks include each action's risks. A proposal that would send the group's funds is risk level
high, one that would retire creditscritical. - Status is checked on chain. Before voting, carrying out or closing, the tool reads the proposal on the preview and again right before signing, and refuses a step that cannot succeed, saying why: its status, the votes against what it needs, and when voting closes. For example, in a group that allows vote changes a proposal stays open until voting closes even with enough yes votes, so carrying it out early is refused with when it can be.
- Carry-outs list their actions. Before a proposal is carried out, the tool reads its actions from the chain and lists them, saying plainly when one cannot be read here (check it in the Portal's Decisions tab).
- The chain decides who may act. Only members vote, with the voting power they had when the proposal opened; only members carry out. When the wallet or the chain still refuses a step, the chain's reason reaches the model with a plain
hint(not a member, already voted, voting closed, not passed, and so on).
Mainnet
Every mainnet request is refused unless IXO_TRANSACTION_ALLOW_MAINNET=true. With it:
- Catalog drafts (
sign_ixo_transaction) need the receipt of the same message signed on testnet by the same user within the last 24 hours. A testnetsignedresult carries atestnetReceipt; the mainnet draft cites it, and the plugin checks it against what it recorded. There is no override. - DAO DAO tools run on a mainnet oracle without a receipt: a group and its proposals exist on one network only.
The Portal side
The Portal answers the sign_transaction action with one hook from @ixo/ixo-transaction/react, mounted inside OraclesProvider with the chain id its wallet is connected to:
import { useIxoTransactionSigningAction } from '@ixo/ixo-transaction/react';
function OracleChat({ walletChainId }: { walletChainId: string }) {
// The chain the wallet is configured for, from your own wallet configuration.
useIxoTransactionSigningAction({ chainId: walletChainId });
return <Chat />;
}The hook validates every message against the catalog, refuses any chain id other than the one it was given, and signs the message (or a batch, as the POD Creator sends) in one wallet transaction. It registers the action so that the model cannot call it directly: only the plugin's signing tools reach it. A client without this handler gets unavailable from every signing tool. The Client SDK provides OraclesProvider.
Known limits
- Timeouts stay unknown. The plugin cannot tell whether a timed-out request was signed later; the user has to check. After a
create_dao_grouptimeout,chain_lookup daoslists the group once it exists. - One conversation at a time. The record of groups and links already sent lives with the conversation and expires after 1 hour without use. The same group confirmed again in a later conversation is a second group.
link_dao_groupdoes not replace or remove a governing group, takesdid:ixo:entity:DIDs only, and reads at most 30 members and 3 proposal lists. Signing through an authz grant is not supported.- DAO DAO coverage is the Portal's multisig group: no proposal deposits, no multiple-choice proposals, at most 4 actions per proposal. A carry-out names only the actions it can decode; the Portal's consent sheet shows the carry-out's own risk, not its actions'.
- Mainnet catalog drafts need a testnet run of the same message within 24 hours.
When to use it
- The user, in a Portal chat, wants to create, update, transfer, submit, evaluate, mint or retire something on the IXO chain.
- The user wants a DAO DAO group, wants a group to govern a POD or entity they control, or wants to propose, vote on, carry out or close a group proposal.
- The user types a slash command such as
/ixo entity createor/ixo token retire.
When NOT to use it
- Reading chain state or balances: use
ixo-chain. - Chats outside the Portal (Matrix, Slack, WhatsApp and other channels): the plugin offers nothing there.
- Asking the oracle to sign: only the user signs, in their wallet.