PageSourceSearch

https://www.arkhai.io/assets/js/e802a62f.25df7e1f.js

js arkhai.io collected 2026-10-08 20:55:55 UTC 42,763 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkarkhai_website=self.webpackChunkarkhai_website||[]).push([[8071],{28453:(e,n,a)=>{a.d(n,{R:()=>d,x:()=>o});var r=a(96540);const t={},i=r.createContext(t);function d(e){const n=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:d(e.components),r.createElement(i.Provider,{value:n},e.children)}},36782:(e,n,a)=>{a.r(n),a.d(n,{assets:()=>s,contentTitle:()=>c,default:()=>b,frontMatter:()=>l,metadata:()=>r,toc:()=>m});const r=JSON.parse('{"id":"Escrow Flow/Composing Demands","title":"pt 3 - Composing Demands","description":"The logical arbiter contracts AnyArbiter and AllArbiter can be used to compose demands. For example, you could demand that","source":"@site/docs/Escrow Flow/Composing Demands.mdx","sourceDirName":"Escrow Flow","slug":"/Escrow Flow/composing-demands","permalink":"/docs/Escrow Flow/composing-demands","draft":false,"unlisted":false,"tags":[],"version":"current","frontMatter":{"title":"pt 3 - Composing Demands","slug":"composing-demands"},"sidebar":"docs","previous":{"title":"pt 2 - Job Trading","permalink":"/docs/Escrow Flow/job-trading"},"next":{"title":"On-chain Arbiters","permalink":"/docs/Writing Arbiters/on-chain-arbiters"}}');var t=a(74848),i=a(28453),d=a(79102),o=a(38860);const l={title:"pt 3 - Composing Demands",slug:"composing-demands"},c="Escrow Flow (pt 3 - Composing Demands)",s={},m=[{value:"Using logical arbiters",id:"using-logical-arbiters",level:2},{value:"Parsing composed demands",id:"parsing-composed-demands",level:2},{value:"Why there's no NotArbiter",id:"why-theres-no-notarbiter",level:2},{value:"The revert problem",id:"the-revert-problem",level:3},{value:"What to do instead",id:"what-to-do-instead",level:3}];function u(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.header,{children:(0,t.jsx)(n.h1,{id:"escrow-flow-pt-3---composing-demands",children:"Escrow Flow (pt 3 - Composing Demands)"})}),"\n",(0,t.jsx)(n.p,{children:"The logical arbiter contracts AnyArbiter and AllArbiter can be used to compose demands. For example, you could demand that"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"a task can only be completed by a particular account, and is validated by a trusted third party"}),"\n",(0,t.jsx)(n.li,{children:"a task is completed before a particular deadline, and validated by any of a list of trusted third parties"}),"\n",(0,t.jsx)(n.li,{children:"uptime for a server provision is above a certain threshold, and is actually the expected service"}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"using-logical-arbiters",children:"Using logical arbiters"}),"\n",(0,t.jsx)(n.p,{children:"You can use AllArbiter to demand multiple conditions at the same time. For example, that your task is completed by a particular individual before a deadline, and validated by a third party."}),"\n",(0,t.jsxs)(d.A,{groupId:"language",children:[(0,t.jsx)(o.A,{value:"solidity",label:"Solidity",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-solidity",children:'// Example: Require job completed by specific recipient AND validated by oracle\nbytes memory recipientDemand = abi.encode(\n    RecipientArbiter.DemandData({\n        recipient: bob  // Only Bob can fulfill this\n    })\n);\n\nbytes memory oracleDemand = abi.encode(\n    TrustedOracleArbiter.DemandData({\n        oracle: charlie,  // Charlie validates the result\n        data: abi.encode("complete the analysis task")\n    })\n);\n\n// Compose with AllArbiter - both conditions must be met\nbytes memory composedDemand = abi.encode(\n    AllArbiter.DemandData({\n        arbiters: [address(recipientArbiter), address(trustedOracleArbiter)],\n        demands: [recipientDemand, oracleDemand]\n    })\n);\n\n// Deposit escrow with the composed demand\nIERC20(erc20Token).approve(address(erc20EscrowObligation), 100 * 10**18);\nbytes32 escrowUid = erc20EscrowObligation.doObligation(\n    ERC20EscrowObligation.ObligationData({\n        token: erc20Token,\n        amount: 100 * 10**18,\n        arbiter: address(allArbiter),\n        demand: composedDemand\n    }),\n    block.timestamp + 86400\n);\n'})})}),(0,t.jsx)(o.A,{value:"typescript",label:"TypeScript",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-typescript",children:'// Example: Require job completed by specific recipient AND validated by oracle\nconst recipientDemand = encodeAbiParameters(\n  parseAbiParameters("(address recipient)"),\n  [{ recipient: bob }
1],\n);\n\nconst oracleDemand = aliceClient.arbiters.encodeTrustedOracleDemand({\n  oracle: charlie,\n  data: encodeAbiParameters(parseAbiParameters("(string query)"), [\n    { query: "complete the analysis task" },\n  ]),\n});\n\n// Compose with AllArbiter - both conditions must be met\nconst composedDemand = aliceClient.arbiters.encodeAllArbiterDemand({\n  arbiters: [recipientArbiter, trustedOracleArbiter],\n  demands: [recipientDemand, oracleDemand],\n});\n\n// Deposit escrow with the composed demand (using permit for gasless approval)\nconst { attested: escrow } = await aliceClient.erc20.escrow.nonTierable.permitAndCreate(\n  { address: erc20Token, value: parseEther("100") },\n  { arbiter: allArbiter, demand: composedDemand },\n  BigInt(Math.floor(Date.now() / 1000) + 86400),\n);\n'})})}),(0,t.jsx)(o.A,{value:"rust",label:"Rust",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rust",children:'use alloy::sol_types::SolValue;\nuse alkahest_rs::clients::arbiters::MultiArbiter;\n\n// Example: Require job completed by specific recipient AND validated by oracle\nlet recipient_demand_data = contracts::RecipientArbiter::DemandData {\n    recipient: bob_address,\n};\nlet recipient_demand = recipient_demand_data.abi_encode().into();\n\nlet oracle_demand_data = contracts::TrustedOracleArbiter::DemandData {\n    oracle: charlie_address,\n    data: "complete the analysis task".abi_encode().into(),\n};\nlet oracle_demand = oracle_demand_data.abi_encode().into();\n\n// Compose with AllArbiter - both conditions must be met\nlet composed_demand_data = MultiArbiter::DemandData {\n    arbiters: vec![recipient_arbiter_address, trusted_oracle_arbiter_address],\n    demands: vec![recipient_demand, oracle_demand],\n};\nlet composed_demand = alice_client\n    .arbiters()\n    .encode_multi_arbiter_demand(&composed_demand_data);\n\n// Deposit escrow with the composed demand (using permit for gasless approval)\nlet price = Erc20Data {\n    address: erc20_token,\n    value: U256::from(100e18),\n};\nlet item = ArbiterData {\n    arbiter: all_arbiter_address,\n    demand: composed_demand,\n};\nlet escrow_receipt = alice_client\n    .erc20()\n    .escrow()\n    .non_tierable()\n    .permit_and_create(&price, &item, expiration_time)\n    .await?;\n'})})}),(0,t.jsx)(o.A,{value:"python",label:"Python",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-python",children:'from eth_abi import encode\nfrom alkahest_py import AllArbiterDemandData, RecipientArbiterDemandData, TrustedOracleArbiterDemandData\n\n# Example: Require job completed by specific recipient AND validated by oracle\nrecipient_demand = RecipientArbiterDemandData(bob).encode_self()\n\noracle_inner_data = encode([\'string\'], [\'complete the analysis task\'])\noracle_demand = TrustedOracleArbiterDemandData(charlie, oracle_inner_data).encode_self()\n\n# Compose with AllArbiter - both conditions must be met\ncomposed_demand = AllArbiterDemandData(\n    [recipient_arbiter, trusted_oracle_arbiter],\n    [recipient_demand, oracle_demand]\n).encode_self()\n\n# Deposit escrow with the composed demand (using permit for gasless approval)\nprice = {"address": erc20_token, "value": 100}\nitem = {"arbiter": all_arbiter, "demand": composed_demand}\nescrow_receipt = await alice_client.erc20.escrow.non_tierable.permit_and_create(\n    price, item, int(time.time()) + 86400\n)\n'})})}),(0,t.jsx)(o.A,{value:"cli",label:"CLI",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'# Encode individual demands\nalkahest arbiter encode-demand --type recipient \\\n  --recipient 0xBOB\n# \u2192 { "encoded": "0xRECIPIENT_DEMAND" }\n\nalkahest arbiter encode-demand --type trusted-oracle \\\n  --oracle 0xCHARLIE --data 0x\n# \u2192 { "encoded": "0xORACLE_DEMAND" }\n\n# Compose with AllArbiter - both conditions must be met\n# Use addresses from: alkahest config show --chain base-sepolia\nalkahest arbiter encode-demand --type all \\\n  --demands \'[{"arbiter":"0xRECIPIENT_ARBITER","demand":"0xRECIPIENT_DEMAND"},{"arbiter":"0xTRUSTED_ORACLE_ARBITER","demand":"0xORACLE_DEMAND"}]\'\n# \u2192 { "encoded": "0xCOMPOSED_DEMAND" }\n\n# Create escrow with the composed demand\nalkahest --private-key 0xALICE_KEY escrow create \\\n  --erc20 \\\n  --token 0xERC20_TOKEN --amount 100000000000000000000 \\\n  --arbiter 0xALL_ARBITER \\\n  --demand 0xCOMPOSED_DEMAND \\\n  --expiration 1735689600 \\\n  --approve\n'})})})]}),"\n",(0,t.jsx)(n.p,{children:"You can use AnyArbiter when multiple alternative conditions can be considered valid. For example, if you accept a decision from any of a list of trusted third party oracles, or if there are different proof mechanisms that equally ensure the validity of a result."}),"\n",(0,t.jsxs)(d.A,{groupId:"language",children:[(0,t.jsx)(o.A,{value:"solidity",label:"Solidity",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-solidity",children:'// Example: Accept validation from ANY of multiple trusted oracles\nbytes memory oracle1Demand = abi.encode(\n    TrustedOracleArbiter.DemandData({\n        oracle: charlie,\n        data: abi.encode("verify computation result")\n    })\n);\n\nbytes memory oracle2Demand = abi.encode(\n    TrustedOracleArbiter.DemandData({\n        oracle: dave,\n        data: abi.encode("verify computation result")\n    })\n);\n\nbytes memory oracle3Demand = abi.encode(\n    TrustedOracleArbiter.DemandData({\n        oracle: eve,\n        data: abi.encode("verify computation result")\n    })\n);\n\n// Compose with AnyArbiter - any one oracle can validate\nbytes memory composedDemand = abi.encode(\n    AnyArbiter.DemandData({\n        arbiters: [\n            address(trustedOracleArbiter),\n            address(trustedOracleArbiter),\n            address(trustedOracleArbiter)\n        ],\n        demands: [oracle1Demand, oracle2Demand, oracle3Demand]\n    })\n);\n\n// Create escrow with flexible oracle validation\nbytes32 escrowUid = erc20EscrowObligation.doObligation(\n    ERC20EscrowObligation.ObligationData({\n        token: erc20Token,\n        amount: 50 * 10**18,\n        arbiter: address(anyArbiter),\n        demand: composedDemand\n    }),\n    block.timestamp + 7200  // 2 hour expiration\n);\n'})})}),(0,t.jsx)(o.A,{value:"typescript",label:"TypeScript",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-typescript",children:'// Example: Accept validation from ANY of multiple trusted oracles\nconst oracle1Demand = aliceClient.arbiters.encodeTrustedOracleDemand({\n  oracle: charlie,\n  data: encodeAbiParameters(parseAbiParameters("(string query
1)"), [\n    { query: "verify computation result" },\n  ]),\n});\n\nconst oracle2Demand = aliceClient.arbiters.encodeTrustedOracleDemand({\n  oracle: dave,\n  data: encodeAbiParameters(parseAbiParameters("(string query)"), [\n    { query: "verify computation result" },\n  ]),\n});\n\nconst oracle3Demand = aliceClient.arbiters.encodeTrustedOracleDemand({\n  oracle: eve,\n  data: encodeAbiParameters(parseAbiParameters("(string query)"), [\n    { query: "verify computation result" },\n  ]),\n});\n\n// Compose with AnyArbiter - any one oracle can validate\nconst composedDemand = aliceClient.arbiters.encodeAnyArbiterDemand({\n  arbiters: [trustedOracleArbiter, trustedOracleArbiter, trustedOracleArbiter],\n  demands: [oracle1Demand, oracle2Demand, oracle3Demand],\n});\n\n// Create escrow with flexible oracle validation (using approveAndCreate)\nconst { attested: escrow } = await aliceClient.erc20.escrow.nonTierable.approveAndCreate(\n  { address: erc20Token, value: parseEther("50") },\n  { arbiter: anyArbiter, demand: composedDemand },\n  BigInt(Math.floor(Date.now() / 1000) + 7200), // 2 hour expiration\n);\n'})})}),(0,t.jsx)(o.A,{value:"rust",label:"Rust",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rust",children:'// Example: Accept validation from ANY of multiple trusted oracles\nlet query = "verify computation result".abi_encode();\n\nlet oracle1_demand = contracts::TrustedOracleArbiter::DemandData {\n    oracle: charlie_address,\n    data: query.clone().into(),\n}.abi_encode().into();\n\nlet oracle2_demand = contracts::TrustedOracleArbiter::DemandData {\n    oracle: dave_address,\n    data: query.clone().into(),\n}.abi_encode().into();\n\nlet oracle3_demand = contracts::TrustedOracleArbiter::DemandData {\n    oracle: eve_address,\n    data: query.into(),\n}.abi_encode().into();\n\n// Compose with AnyArbiter - any one oracle can validate\nlet composed_demand_data = MultiArbiter::DemandData {\n    arbiters: vec![\n        trusted_oracle_arbiter_address,\n        trusted_oracle_arbiter_address,\n        trusted_oracle_arbiter_address,\n    ],\n    demands: vec![oracle1_demand, oracle2_demand, oracle3_demand],\n};\nlet composed_demand = alice_client\n    .arbiters()\n    .encode_multi_arbiter_demand(&composed_demand_data);\n\n// Create escrow with flexible oracle validation (using approve_and_create)\nlet price = Erc20Data {\n    address: erc20_token,\n    value: U256::from(50e18),\n};\nlet item = ArbiterData {\n    arbiter: any_arbiter_address,\n    demand: composed_demand,\n};\nlet escrow_receipt = alice_client\n    .erc20()\n    .escrow()\n    .non_tierable()\n    .approve_and_create(&price, &item, current_timestamp + 7200) // 2 hour expiration\n    .await?;\n'})})}),(0,t.jsx)(o.A,{value:"python",label:"Python",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-python",children:'# Example: Accept validation from ANY of multiple trusted oracles\nquery_data = encode([\'string\'], [\'verify computation result\'])\n\noracle1_demand = TrustedOracleArbiterDemandData(charlie, query_data).encode_self()\noracle2_demand = TrustedOracleArbiterDemandData(dave, query_data).encode_self()\noracle3_demand = TrustedOracleArbiterDemandData(eve, query_data).encode_self()\n\n# Compose with AnyArbiter - any one oracle can validate\ncomposed_demand = AnyArbiterDemandData(\n    [trusted_oracle_arbiter, trusted_oracle_arbiter, trusted_oracle_arbiter],\n    [oracle1_demand, oracle2_demand, oracle3_demand]\n).encode_self()\n\n# Create escrow with flexible oracle validation (using approve_and_create)\nprice = {"address": erc20_token, "value": 50}\nitem = {"arbiter": any_arbiter, "demand": composed_demand}\nescrow_receipt = await alice_client.erc20.escrow.non_tierable.approve_and_create(\n    price, item, int(time.time()) + 7200  # 2 hour expiration\n)\n'})})}),(0,t.jsx)(o.A,{value:"cli",label:"CLI",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'# Encode individual oracle demands\nalkahest arbiter encode-demand --type trusted-oracle \\\n  --oracle 0xCHARLIE --data 0x\n# \u2192 { "encoded": "0xORACLE1_DEMAND" }\n\nalkahest arbiter encode-demand --type trusted-oracle \\\n  --oracle 0xDAVE --data 0x\n# \u2192 { "encoded": "0xORACLE2_DEMAND" }\n\nalkahest arbiter encode-demand --type trusted-oracle \\\n  --oracle 0xEVE --data 0x\n# \u2192 { "encoded": "0xORACLE3_DEMA
1ND" }\n\n# Compose with AnyArbiter - any one oracle can validate\nalkahest arbiter encode-demand --type any \\\n  --demands \'[{"arbiter":"0xTRUSTED_ORACLE_ARBITER","demand":"0xORACLE1_DEMAND"},{"arbiter":"0xTRUSTED_ORACLE_ARBITER","demand":"0xORACLE2_DEMAND"},{"arbiter":"0xTRUSTED_ORACLE_ARBITER","demand":"0xORACLE3_DEMAND"}]\'\n# \u2192 { "encoded": "0xCOMPOSED_DEMAND" }\n\n# Create escrow with flexible oracle validation\nalkahest --private-key 0xALICE_KEY escrow create \\\n  --erc20 \\\n  --token 0xERC20_TOKEN --amount 50000000000000000000 \\\n  --arbiter 0xANY_ARBITER \\\n  --demand 0xCOMPOSED_DEMAND \\\n  --expiration 1735689600 \\\n  --approve\n'})})})]}),"\n",(0,t.jsx)(n.p,{children:"Logical arbiters can be stacked. For example, you could demand that a job is completed before a deadline by a particular party, and validated by any of a list of trusted oracles."}),"\n",(0,t.jsxs)(d.A,{groupId:"language",children:[(0,t.jsx)(o.A,{value:"solidity",label:"Solidity",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-solidity",children:'// Example: Complex composition - deadline AND recipient AND (oracle1 OR oracle2)\nbytes memory deadlineDemand = abi.encode(\n    TimeBeforeArbiter.DemandData({\n        time: uint64(block.timestamp + 3600)  // Must complete within 1 hour\n    })\n);\n\nbytes memory recipientDemand = abi.encode(\n    RecipientArbiter.DemandData({\n        recipient: bob  // Must be fulfilled by Bob\n    })\n);\n\n// Create the OR condition for oracles\nbytes memory oracle1Demand = abi.encode(\n    TrustedOracleArbiter.DemandData({\n        oracle: charlie,\n        data: abi.encode("validate result")\n    })\n);\n\nbytes memory oracle2Demand = abi.encode(\n    TrustedOracleArbiter.DemandData({\n        oracle: dave,\n        data: abi.encode("validate result")\n    })\n);\n\nbytes memory oracleOrDemand = abi.encode(\n    AnyArbiter.DemandData({\n        arbiters: [address(trustedOracleArbiter), address(trustedOracleArbiter)],\n        demands: [oracle1Demand, oracle2Demand]\n    })\n);\n\n// Combine all conditions with AllArbiter\nbytes memory finalDemand = abi.encode(\n    AllArbiter.DemandData({\n        arbiters: [\n            address(timeBeforeArbiter),\n            address(recipientArbiter),\n            address(anyArbiter)\n        ],\n        demands: [\n            deadlineDemand,\n            recipientDemand,\n            oracleOrDemand\n        ]\n    })\n);\n\n// Create the escrow with nested logical arbiters\nbytes32 escrowUid = erc20EscrowObligation.doObligation(\n    ERC20EscrowObligation.ObligationData({\n        token: erc20Token,\n        amount: 200 * 10**18,\n        arbiter: address(allArbiter),\n        demand: finalDemand\n    }),\n    block.timestamp + 7200  // 2 hour overall expiration\n);\n'})})}),(0,t.jsx)(o.A,{value:"typescript",label:"TypeScript",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-typescript",children:'// Example: Complex composition - deadline AND recipient AND (oracle1 OR oracle2)\nconst deadlineDemand = encodeAbiParameters(\n  parseAbiParameters("(uint64 time)"),\n  [{ time: BigInt(Math.floor(Date.now() / 1000) + 3600) }], // 1 hour deadline\n);\n\nconst recipientDemand = encodeAbiParameters(\n  parseAbiParameters("(address recipient)"),\n  [{ recipient: bob }],\n);\n\n// Create the OR condition for oracles\nconst oracle1Demand = aliceClient.arbiters.encodeTrustedOracleDemand({\n  oracle: charlie,\n  data: encodeAbiParameters(parseAbiParameters("(string query)"), [\n    { query: "validate result" },\n  ]),\n});\n\nconst oracle2Demand = aliceClient.arbiters.encodeTrustedOracleDemand({\n  oracle: dave,\n  data: encodeAbiParameters(parseAbiParameters("(string query)"), [\n    { query: "validate result" },\n  ]),\n});\n\nconst oracleOrDemand = aliceClient.arbiters.encodeAnyArbiterDemand({\n  arbiters: [trustedOracleArbiter, trustedOracleArbiter],\n  demands: [oracle1Demand, oracle2Demand],\n});\n\n// Combine all conditions with AllArbiter\nconst finalDemand = aliceClient.arbiters.encodeAllArbiterDemand({\n  arbiters: [timeBeforeArbiter, recipientArbiter, anyArbiter],\n  demands: [deadlineDemand, recipientDemand, oracleOrDemand],\n});
1\n\n// Create the escrow with nested logical arbiters (using approveAndCreate)\nconst { attested: escrow } = await aliceClient.erc20.escrow.nonTierable.approveAndCreate(\n  { address: erc20Token, value: parseEther("200") },\n  { arbiter: allArbiter, demand: finalDemand },\n  BigInt(Math.floor(Date.now() / 1000) + 7200), // 2 hour overall expiration\n);\n'})})}),(0,t.jsx)(o.A,{value:"rust",label:"Rust",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rust",children:'// Example: Complex composition - deadline AND recipient AND (oracle1 OR oracle2)\nlet deadline_demand_data = contracts::TimeBeforeArbiter::DemandData {\n    time: current_timestamp + 3600, // 1 hour deadline\n};\nlet deadline_demand = deadline_demand_data.abi_encode().into();\n\nlet recipient_demand_data = contracts::RecipientArbiter::DemandData {\n    recipient: bob_address,\n};\nlet recipient_demand = recipient_demand_data.abi_encode().into();\n\n// Create the OR condition for oracles\nlet validate_query = "validate result".abi_encode();\n\nlet oracle1_demand = contracts::TrustedOracleArbiter::DemandData {\n    oracle: charlie_address,\n    data: validate_query.clone().into(),\n}.abi_encode().into();\n\nlet oracle2_demand = contracts::TrustedOracleArbiter::DemandData {\n    oracle: dave_address,\n    data: validate_query.into(),\n}.abi_encode().into();\n\nlet oracle_or_demand_data = MultiArbiter::DemandData {\n    arbiters: vec![trusted_oracle_arbiter_address, trusted_oracle_arbiter_address],\n    demands: vec![oracle1_demand, oracle2_demand],\n};\nlet oracle_or_demand = alice_client\n    .arbiters()\n    .encode_multi_arbiter_demand(&oracle_or_demand_data);\n\n// Combine all conditions with AllArbiter\nlet final_demand_data = MultiArbiter::DemandData {\n    arbiters: vec![\n        time_before_arbiter_address,\n        recipient_arbiter_address,\n        any_arbiter_address,\n    ],\n    demands: vec![deadline_demand, recipient_demand, oracle_or_demand],\n};\nlet final_demand = alice_client\n    .arbiters()\n    .encode_multi_arbiter_demand(&final_demand_data);\n\n// Create the escrow with nested logical arbiters (using approve_and_create)\nlet price = Erc20Data {\n    address: erc20_token,\n    value: U256::from(200e18),\n};\nlet item = ArbiterData {\n    arbiter: all_arbiter_address,\n    demand: final_demand,\n};\nlet escrow_receipt = alice_client\n    .erc20()\n    .escrow()\n    .non_tierable()\n    .approve_and_create(&price, &item, current_timestamp + 7200) // 2 hour overall expiration\n    .await?;\n'})})}),(0,t.jsx)(o.A,{value:"python",label:"Python",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-python",children:'# Example: Complex composition - deadline AND recipient AND (oracle1 OR oracle2)\nfrom alkahest_py import TimeBeforeArbiterDemandData, RecipientArbiterDemandData\n\ndeadline_demand = TimeBeforeArbiterDemandData(int(time.time()) + 3600).encode_self()\nrecipient_demand = RecipientArbiterDemandData(bob).encode_self()\n\n# Create the OR condition for oracles\nvalidate_query = encode([\'string\'], [\'validate result\'])\noracle1_demand = TrustedOracleArbiterDemandData(charlie, validate_query).encode_self()\noracle2_demand = TrustedOracleArbiterDemandData(dave, validate_query).encode_self()\n\noracle_or_demand = AnyArbiterDemandData(\n    [trusted_oracle_arbiter, trusted_oracle_arbiter],\n    [oracle1_demand, oracle2_demand]\n).encode_self()\n\n# Combine all conditions with AllArbiter\nfinal_demand = AllArbiterDemandData(\n    [time_before_arbiter, recipient_arbiter, any_arbiter],\n    [deadline_demand, recipient_demand, oracle_or_demand]\n).encode_self()\n\n# Create the escrow with nested logical arbiters (using approve_and_create)\nprice = {"address": erc20_token, "value": 200}\nitem = {"arbiter": all_arbiter, "demand": final_demand}\nescrow_receipt = await alice_client.erc20.escrow.non_tierable.approve_and_create(\n    price, item, int(time.time()) + 7200  # 2 hour overall expiration\n)\n'})})}),(0,t.jsx)(o.A,{value:"cli",label:"CLI",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:'# Encode individual demands\nalkahest arbiter encode-demand --type time-before --time 1735693200\n# \u2192 { "encoded": "0xDEADLINE_DEMAND" }\n\nalkahest arbiter encode-demand --type recipient --recipient 0xBOB\n# \u2192 { "encoded": "0xRECIPIENT_DEMAND" }\n\nalkahest arbiter encode-demand --type trusted-oracle --oracle 0xCHARLIE --data 0x\n# \u2192 { "encoded": "0xORACLE1_DEMAND" }\n\nalkahest arbiter encode-demand --type trusted-oracle --oracle 0xDAVE --data 0x\n# \u2192 { "encoded": "0xORACLE2_DEMAND" }\n\n# Create the OR condition for oracles\nalkahest arbiter encode-demand --type any \\\n  --demands \'[{"arbiter":"0xTRUSTED_ORACLE_ARBITER","demand":"0xORACLE1_DEMAND"},{"arbiter":"0xTRUSTED_ORACLE_ARBITER","demand":"0xORACLE2_DEMAND"}]\'\n# \u2192 { "encoded": "0xORACLE_OR_DEMAND" }\n\n# Combine all conditions with AllArbiter: deadline AND recipient AND (oracle1 OR oracle2)\nalkahest arbiter encode-demand --type all \\\n  --demands \'[{"arbiter":"0xTIME_BEFORE_ARBITER","demand":"0xDEADLINE_DEMAND"},{"arbiter":"0xRECIPIENT_ARBITER","demand":"0xRECIPIENT_DEMAND"},{"arbiter":"0xANY_ARBITER","demand":"0xORACLE_OR_DEMAND"}]\'\n# \u2192 { "encoded": "0xFINAL_DEMAND" }
1\n\n# Create the escrow with nested logical arbiters\nalkahest --private-key 0xALICE_KEY escrow create \\\n  --erc20 \\\n  --token 0xERC20_TOKEN --amount 200000000000000000000 \\\n  --arbiter 0xALL_ARBITER \\\n  --demand 0xFINAL_DEMAND \\\n  --expiration 1735700400 \\\n  --approve\n'})})})]}),"\n",(0,t.jsx)(n.h2,{id:"parsing-composed-demands",children:"Parsing composed demands"}),"\n",(0,t.jsx)(n.p,{children:"The CLI and SDKs provide built-in support for recursively parsing composed demands. The parsers automatically detect the arbiter type and decode each sub-demand appropriately."}),"\n",(0,t.jsxs)(d.A,{groupId:"language",children:[(0,t.jsx)(o.A,{value:"solidity",label:"Solidity",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-solidity",children:'// Example: Validate that on-chain demand matches expected structure\nfunction validateDemand(\n    bytes memory onChainDemand,\n    bytes memory expectedDemand\n) public pure returns (bool) {\n    // Simply check if the demands match\n    return keccak256(onChainDemand) == keccak256(expectedDemand);\n}\n\n// Or decode and validate specific parts\nfunction parseAllArbiterDemand(bytes memory demand)\n    public\n    pure\n    returns (address[] memory arbiters, bytes[] memory demands)\n{\n    AllArbiter.DemandData memory demandData = abi.decode(\n        demand,\n        (AllArbiter.DemandData)\n    );\n    return (demandData.arbiters, demandData.demands);\n}\n\n// Example usage\n(address[] memory arbiters, bytes[] memory subDemands) = parseAllArbiterDemand(composedDemand);\nrequire(arbiters[0] == address(timeBeforeArbiter), "First arbiter must be TimeBeforeArbiter");\nrequire(arbiters[1] == address(recipientArbiter), "Second arbiter must be RecipientArbiter");\n'})})}),(0,t.jsx)(o.A,{value:"typescript",label:"TypeScript",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-typescript",children:'import { decodeDemandWithAddresses } from "alkahest-ts/utils/demandParsing";\n\n// Use the built-in recursive demand parser with chain addresses\nconst decoded = decodeDemandWithAddresses(\n  { arbiter: allArbiter, demand: composedDemand },\n  client.addresses,\n);\n\n// The result includes arbiter, decoded data, and children for logical arbiters\nconsole.log("Top-level arbiter:", decoded.arbiter);\nconsole.log("Decoded data:", decoded.decoded);\n\n// For logical arbiters (AllArbiter, AnyArbiter), children are recursively decoded\nif (decoded.children) {\n  for (const child of decoded.children) {\n    console.log("Child arbiter:", child.arbiter);\n    console.log("Child data:", child.decoded);\n    // Children can also have their own children for nested logical arbiters\n    if (child.children) {\n      console.log("Nested children:", child.children);\n    }\n  }\n}\n\n// Check if an arbiter was unknown (not in the registered decoders)\nif (decoded.isUnknown) {\n  console.log("Unknown arbiter, raw demand:", decoded.decoded.raw);\n}\n'})})}),(0,t.jsx)(o.A,{value:"rust",label:"Rust",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-rust",children:'use alkahest_rs::contracts::arbiters::logical::AllArbiter as AllArbiterContract;\n\n// Decode AllArbiter demand data from raw bytes\nlet demand_data: AllArbiterContract::DemandData =\n    AllArbiterContract::DemandData::abi_decode(&composed_demand, true)?;\n\n// Use the built-in recursive decoder\nlet decoded = alice_client\n    .arbiters()\n    .logical()\n    .all()\n    .decode(demand_data)?;\n\n// The result contains arbiters and recursively decoded demands\nprintln!("Arbiters: {:?}", decoded.arbiters);\n\n// Each demand is decoded into a typed enum variant\nfor demand in &decoded.demands {\n    match demand {\n        DecodedDemand::TimeBeforeArbiter(data) => {\n            println!("TimeBeforeArbiter: time={}", data.time);\n        }\n        DecodedDemand::RecipientArbiter(data) => {\n            println!("RecipientArbiter: recipient={}", data.recipient);\n        }\n        DecodedDemand::TrustedOracle(data) => {\n            println!("TrustedOracle: oracle={}", data.oracle);\n        }\n        DecodedDemand::AnyArbiter(nested) =>
1 {\n            // Nested logical arbiters are also decoded\n            println!("Nested AnyArbiter with {} sub-demands", nested.demands.len());\n        }\n        DecodedDemand::AllArbiter(nested) => {\n            println!("Nested AllArbiter with {} sub-demands", nested.demands.len());\n        }\n        DecodedDemand::Unknown { arbiter, raw_data } => {\n            println!("Unknown arbiter {}: {:?}", arbiter, raw_data);\n        }\n        _ => println!("Other demand type"),\n    }\n}\n'})})}),(0,t.jsx)(o.A,{value:"python",label:"Python",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-python",children:'# Use the built-in recursive decoder\ndecoded = alice_client.arbiters.logical.all.decode(composed_demand)\n\n# The result contains arbiters and recursively decoded demands\nprint(f"Arbiters: {decoded.arbiters}")\n\n# Each demand has a type and optional raw data\nfor demand in decoded.demands:\n    print(f"Demand type: {demand.demand_type}")\n    if demand.raw_data:\n        # Raw data can be further decoded based on the demand type\n        if demand.demand_type == "TimeBeforeArbiter":\n            time_value = decode([\'uint64\'], demand.raw_data)[0]\n            print(f"  Time: {datetime.fromtimestamp(time_value)}")\n        elif demand.demand_type == "RecipientArbiter":\n            recipient = decode([\'address\'], demand.raw_data)[0]\n            print(f"  Recipient: {recipient}")\n        elif demand.demand_type == "TrustedOracle":\n            oracle, data = decode([\'address\', \'bytes\'], demand.raw_data)\n            print(f"  Oracle: {oracle}")\n\n# For AnyArbiter, use the any decoder\ndecoded_any = alice_client.arbiters.logical.any.decode(any_arbiter_demand)\nprint(f"AnyArbiter sub-demands: {len(decoded_any.demands)}")\n'})})}),(0,t.jsx)(o.A,{value:"cli",label:"CLI",children:(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-bash",children:"# Decode a composed demand given the arbiter address and encoded demand hex\nalkahest arbiter decode-demand \\\n  --arbiter 0xALL_ARBITER \\\n  --demand 0xCOMPOSED_DEMAND_HEX\n# \u2192 Recursively decoded demand structure as JSON\n"})})})]}),"\n",(0,t.jsx)(n.p,{children:"For custom arbiters not included in the SDK's built-in decoders, you can extend the decoder registry (TypeScript) or manually decode the raw bytes using ABI decoding."}),"\n",(0,t.jsx)(n.h2,{id:"why-theres-no-notarbiter",children:"Why there's no NotArbiter"}),"\n",(0,t.jsxs)(n.p,{children:["You might expect a ",(0,t.jsx)(n.code,{children:"NotArbiter"})," to complement ",(0,t.jsx)(n.code,{children:"AllArbiter"})," and ",(0,t.jsx)(n.code,{children:"AnyArbiter"}),", allowing you to negate any arbiter's logic. However, Alkahest intentionally does not include a ",(0,t.jsx)(n.code,{children:"NotArbiter"})," due to fundamental security concerns."]}),"\n",(0,t.jsx)(n.h3,{id:"the-revert-problem",children:"The revert problem"}),"\n",(0,t.jsxs)(n.p,{children:["Arbiters typically signal failure by ",(0,t.jsx)(n.strong,{children:"reverting"})," rather than returning ",(0,t.jsx)(n.code,{children:"false"}),'. This allows them to provide meaningful error messages explaining why arbitration failed (e.g., "recipient mismatch" or "deadline passed"). A ',(0,t.jsx)(n.code,{children:"NotArbiter"})," would need to catch these reverts and invert the result, but this creates a critical ambiguity:"]}),"\n",(0,t.jsx)(n.p,{children:(0,t.jsxs)(n.strong,{children:["A ",(0,t.jsx)(n.code,{children:"NotArbiter"})," cannot distinguish between:"]})}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Logical reversions"})," - The arbiter intentionally reverted because its condition wasn't met (e.g., wrong recipient)"]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.strong,{children:"Execution reversions"})," - The call failed for technical reasons (e.g., out of gas, invalid calldata, contract bug)"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["If a ",(0,t.jsx)(n.code,{children:"NotArbiter"}),' treated all reverts as "condition not met" and returned success, it would incorrectly pass arbitration when calls fail due to out-of-gas attacks, malformed data, or other execution errors. This could allow attackers to bypass security checks.']}),"\n",(0,t.jsx)(n.h3,{id:"what-to-do-instead",children:"What to do instead"}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Use built-in complement arbiters."})," Most arbiters with useful complements already have them:"]}),"\n",(0,t.jsxs)(n.table,{children:[(0,t.jsx)(n.thead,{children:(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.th,{children:"Arbiter"}),(0,t.jsx)(n.th,{children:"Complement"})]})}),(0,t.jsxs)(n.tbody,{children:[(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"TimeBeforeArbiter"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"TimeAfterArbiter"})})]}),(0,t.jsxs)(n.tr,{children:[(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"ExpirationTimeBeforeArbiter"})}),(0,t.jsx)(n.td,{children:(0,t.jsx)(n.code,{children:"ExpirationTimeAfterArbiter"})})]})]})]}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Write a standalone inverting arbiter."})," Fork or rewrite the arbiter with inverted logic. This is the safest approach since you explicitly implement the complement:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-solidity",children:"// Example: Standalone arbiter that inverts RecipientArbiter logic\n// (allows anyone EXCEPT a specific address)\ncontract NotRecipientArbiter is IArbiter {\n    struct DemandData {\n        address excludedRecipient;
1\n    }\n\n    function checkStatement(\n        Attestation memory statement,\n        bytes memory demand,\n        bytes32 /* counteroffer */\n    ) external pure returns (bool) {\n        DemandData memory data = abi.decode(demand, (DemandData));\n        // Explicitly check the inverse condition\n        return statement.recipient != data.excludedRecipient;\n    }\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:[(0,t.jsx)(n.strong,{children:"Subcall the original arbiter with explicit error handling."})," If you want to wrap an existing arbiter, you must explicitly handle its known revert reasons rather than catching all reverts:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-solidity",children:"// Example: Inverting arbiter that subcalls the original\ncontract InvertingArbiter is IArbiter {\n    IArbiter public immutable underlying;\n\n    constructor(IArbiter _underlying) {\n        underlying = _underlying;\n    }\n\n    function checkStatement(\n        Attestation memory statement,\n        bytes memory demand,\n        bytes32 counteroffer\n    ) external view returns (bool) {\n        try underlying.checkStatement(statement, demand, counteroffer) returns (bool result) {\n            return !result;\n        } catch (bytes memory reason) {\n            // CRITICAL: Only catch KNOWN logical revert reasons\n            // Unknown reverts (OOG, invalid data) must propagate\n            bytes4 selector = bytes4(reason);\n            if (selector == RecipientMismatch.selector) {\n                return true; // Logical failure -> inverted success\n            }\n            if (selector == DeadlinePassed.selector) {\n                return true; // Logical failure -> inverted success\n            }\n            // Unknown error - do NOT treat as success, propagate it\n            assembly {\n                revert(add(reason, 32), mload(reason))\n            }\n        }\n    }\n}\n"})}),"\n",(0,t.jsx)(n.p,{children:'This approach requires you to enumerate every logical revert reason the underlying arbiter can produce. Unknown reverts (out of gas, invalid calldata, bugs) propagate rather than being incorrectly treated as "condition not met."'})]})}function b(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(u,{...e})}):u(e)}},38860:(e,n,a)=>{a.d(n,{A:()=>d});a(96540);var r=a(34164);const t={tabItem:"tabItem_s4ch"};var i=a(74848);function d(e){var n=e.children,a=e.hidden,d=e.className;return(0,i.jsx)("div",{role:"tabpanel",className:(0,r.A)(t.tabItem,d),hidden:a,children:n})}},79102:(e,n,a)=>{a.d(n,{A:()=>f});var r=a(96540),t=a(34164),i=a(17559),d=a(23104),o=a(56347),l=a(205),c=a(57485);var s=a(70679);function m(e){var n,a;return null!=(n=null==(a=r.Children.toArray(e).filter(function(e){return"\n"!==e}).map(function(e){if(!e||(0,r.isValidElement)(e)&&((n=e.props)&&"object"==typeof n&&"value"in n))return e;var n;throw new Error("Docusaurus error: Bad <Tabs> child <"+("string"==typeof e.type?e.type:e.type.name)+'>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.')}))?void 0:a.filter(Boolean))?n:[]}function u(e){var n=e.values,a=e.children;return(0,r.useMemo)(function(){var e=null!=n?n:function(e){return m(e).map(function(e){var n=e.props;return{value:n.value,label:n.label,attributes:n.attributes,default:n.default}})}(a);return function(e){var n,a,r=(void 0===(a=function(e,n){return e.value===n.value})&&(a=function(e,n){return e===n}),(n=e).filter(function(e,r){return n.findIndex(function(n){return a(n,e)})!==r}));if(r.length>0)throw new Error('Docusaurus error: Duplicate values "'+r.map(function(e){return e.value}).join(", ")+'" found in <Tabs>. Every value needs to be unique.')}(e),e},[n,a])}function b(e){var n=e.value;return e.tabValues.some(function(e){return e.value===n})}function p(e){var n=e.queryString,a=void 0!==n&&n,t=e.groupId,i=(0,o.W6)(),d=function(e){var n=e.queryString,a=void 0!==n&&n,r=e.groupId;if("string"==typeof a)return a;if(!1===a)return null;if(!0===a&&!r)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return null!=r?r:null}({queryString:a,groupId:t});return[(0,c.aZ)(d),(0,r.useCallback)(function(e){if(d){var n=new URLSearchParams(i.location.search);n.set(d,e),i.replace(Object.assign({},i.location,{search:n.toString()}))}},[d,i])]}function h(e){var n,a,t,i,d=e.defaultValue,o=e.queryString,c=void 0!==o&&o,m=e.groupId,h=u(e),A=(0,r.useState)(function(){return function(e){var n,a=e.defaultValue,r=e.tabValues;if(0===r.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(a){if(!b({value:a,tabValues:r}))throw new Error('Docusaurus error: The <Tabs> has a defaultValue "'+a+'" but none of its children has the corresponding value. Available values are: '+r.map(function(e){return e.value}).join(", ")+". If you intend to show no default tab, use defaultValue={null} instead.");return a}var t=null!=(n=r.find(function(e){return e.default}))?n:r[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:d,tabValues:h})}),_=A[0],D=A[1],y=p({queryString:c,groupId:m}),x=y[0],v=y[1],f=(n=function(e){return e?"docusaurus.tab."+e:null}({groupId:m}.groupId),a=(0,s.Dv)(n),t=a[0],i=a[1],[t,(0,r.useCallback)(function(e){n&&i.set(e)},[n,i])]),g=f[0],w=f[1],E=function(){var e=null!=x?x:g;return b({value:e,tabValues:h})?e:null}();return(0,l.A)(function(){E&&D(E)},[E]),{selectedValue:_,selectValue:(0,r.useCallback)(function(e){if(!b({value:e,tabValues:h}))throw new Error("Can't select invalid tab value="+e);D(e),v(e),w(e)},[v,w,h]),tabValues:h}}var A=a(92303);const _={tabList:"tabList_TRJ7",tabItem:"tabItem_hGfb"};var D=a(74848);function y(e){var n=e.className,a=e.block,r=e.selectedValue,i=e.selectValue,o=e.tabValues,l=[],c=(0,d.a_)().blockElementScrollPositionUntilNextRender,s=function(e){var n=e.currentTarget,a=l.indexOf(n),t=o[a].value;t!==r&&(c(n),i(t))},m=function(e){var n,a=null;switch(e.key){case"Enter":s(e);break;case"ArrowRight":var r,t=l.indexOf(e.currentTarget)+1;a=null!=(r=l[t])?r:l[0];break;case"ArrowLeft":var i,d=l.indexOf(e.currentTarget)-1;a=null!=(i=l[d])?i:l[l.length-1]}null==(n=a)||n.focus()};return(0,D.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,t.A)("tabs",{"tabs--block":a},n),children:o.map(function(e){var n=e.value,a=e.label,i=e.attributes;return(0,D.jsx)("li",Object.assign({role:"tab",tabIndex:r===n?0:-1,"aria-selected":r===n,ref:function(e){l.push(e)},onKeyDown:m,onClick:s},i,{className:(0,t.A)("tabs__item",_.tabItem,null==i?void 0:i.className,{"tabs__item--active":r===n}),children:null!=a?a:n}),n)})})}function x(e){var n=e.lazy,a=e.children,i=e.selectedValue,d=(Array.isArray(a)?a:[a]).filter(
1Boolean);if(n){var o=d.find(function(e){return e.props.value===i});return o?(0,r.cloneElement)(o,{className:(0,t.A)("margin-top--md",o.props.className)}):null}return(0,D.jsx)("div",{className:"margin-top--md",children:d.map(function(e,n){return(0,r.cloneElement)(e,{key:n,hidden:e.props.value!==i})})})}function v(e){var n=h(e);return(0,D.jsxs)("div",{className:(0,t.A)(i.G.tabs.container,"tabs-container",_.tabList),children:[(0,D.jsx)(y,Object.assign({},n,e)),(0,D.jsx)(x,Object.assign({},n,e))]})}function f(e){var n=(0,A.A)();return(0,D.jsx)(v,Object.assign({},e,{children:m(e.children)}),String(n))}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.