{"ok":true,"schema":"taifoon.partners.v1","chain_id":36927,"chain":"Taifoon devnet","rpc":"https://rpc.taifoon.dev","contracts":{"partner_bond":{"name":"PartnerBond (devnet, dUSDC)","address":"0x36E030CAC080DF6B62f45Ea3f20BE06758a5Da03","since":"2026-10-04","deploy":{"tx":"0x0779c10bd79f2241b859fb9a123741dcf468257fcaf610cb9763140074b1905c","block":1388114}},"hook":{"name":"AssuranceHook V4 (proxy, devnet, dUSDC, partner bond)","address":"0xd65d173cd4f83Dc1663C845269E57703FaC61979","since":"2026-10-04","deploy":{"tx":"0xfed3bcbe8d94e8d2dd6c548e391794559cbe30b78a65f1b76c17f3575ed0af78","block":1388104}},"asset":{"name":"dUSDC (MockUSDC)","symbol":"dUSDC","address":"0x31854D915F31A1FBe57c2b06B5b60cbbD7D42D5b","decimals":6}},"notice_s":604800,"notice":"7 days (PartnerBond.NOTICE)","namespace":"a short label is hashed as keccak256(utf8(label)), case kept; a 0x… 32-byte value is used as sent","amounts":"decimal strings in dUSDC (6 decimals): \"10\" = 10000000 smallest units","actions":["enrol","top_up","mint","set_cap","retire","request_withdraw","cancel_withdraw","withdraw","open_job","accept","submit","complete"],"routes":{"lane":"GET /v1/partners","partner":"GET /v1/partners/:ns — owner, bond, capSum, free, the pending withdrawal and its members, read live","member":"GET /v1/partners/:ns/members/:member — cap, reserved, headroom, active, payout, read live","plan":"POST /v1/partners/plan { action, … } — the unsigned calls, nothing sent, no key"},"lane":{"rule":"Phase 5: an enterprise partner enrols a namespace, posts ONE bond, and adds members (sellers it vouches for) each with a cap; a job naming a member reserves part of that cap; an adjudicated loss slashes the bond up to the reservation. PartnerBond has no owner, no admin, no pause, no upgrade and no sweep; only the hook reserves, releases and slashes","contract":"protocols/ours/partner-bond/src/PartnerBond.sol","wiring":"protocols/ours/assurance-v4/src/AssuranceHook.sol (board items 15 and 20)","states":[{"id":"Unenrolled","what":"no partner holds the namespace"},{"id":"Enrolled","what":"a partner holds the namespace and its bond; capSum ≤ bond at every step"},{"id":"MemberActive","what":"a member with a cap; may be named on new jobs"},{"id":"Reserved","what":"a job naming the member holds part of its cap (reserved ≤ cap)"},{"id":"Retired","what":"the member takes no new job; its cap falls to what open jobs still hold"},{"id":"WithdrawPending","what":"the partner asked to take back what stands behind no cap; it waits the 7-day notice"}],"transitions":[{"id":"enrol","from":"Unenrolled","to":"Enrolled","call":"enrol(ns, amount)","event":"Enrolled","actor":"partner","guard":"namespace not taken; amount > 0; first come, for good"},{"id":"top_up","from":"Enrolled","to":"Enrolled","call":"topUp(ns, amount)","event":"ToppedUp","actor":"partner","guard":"only the partner (a stranger’s deposit is refused)"},{"id":"mint","from":"Enrolled","to":"MemberActive","call":"mint(ns, member, payout, cap)","event":"MemberMinted","actor":"partner","guard":"capSum + cap ≤ bond"},{"id":"set_cap","from":"MemberActive","to":"MemberActive","call":"setCap(ns, member, cap)","event":"CapSet","actor":"partner","guard":"cap ≥ reserved; capSum ≤ bond"},{"id":"reserve","from":"MemberActive","to":"Reserved","call":"reserve(jobId, ns, member, price) — by the hook in accept(jobId) of a job opened with openJobWithBond(salt, terms, partnerNs)","event":"Reserved","actor":"hook","guard":"member active; price ≤ cap − reserved; one reservation per job; the bond asset is the job token; the buyer is neither the member nor the partner owner (checked at open and again at accept); a refusal reverts the accept, nothing is held"},{"id":"release","from":"Reserved","to":"MemberActive","call":"release(jobId) — by the hook on complete, auto-complete, a dispute the seller won, reject and expire","event":"Released","actor":"hook","guard":"a raw call to the bond the JOB recorded at accept (bondOf): a reverting bond, one with no code or a bad return never blocks the ending (BondReleaseFailed); swapping or unsetting the hook’s bond never strands an open job"},{"id":"slash","from":"Reserved","to":"MemberActive","call":"slash(jobId, buyer, remainder) — by the hook in findCheated, after the deposit","event":"Slashed","actor":"hook (on an arbitrator’s finding)","guard":"paid ≤ the job’s reservation; what the bond paid is the buyer’s measured balance change; the hook emits CheatPaidFrom(jobId, fromDeposit, fromBond, fromPool)"},{"id":"retire","from":"MemberActive","to":"Retired","call":"retire(ns, member)","event":"MemberRetired","actor":"partner","guard":"open reservations stay; the cap follows them down"},{"id":"request_withdraw","from":"Enrolled","to":"WithdrawPending","call":"requestWithdraw(ns, amount)","event":"WithdrawRequested","actor":"partner","guard":"amount ≤ bond − capSum (what stands behind no cap)"},{"id":"withdraw","from":"WithdrawPending","to":"Enrolled","call":"withdraw(ns) after 7 days","event":"Withdrawn","actor":"partner","guard":"checked again at the time: a slash or a raised cap during the notice makes it not free"}],"payout_order":{"decided":"the owner, 2026-10-04 (the spec’s order), with the guards below","on":"findCheated(jobId, loss): the buyer’s own escrow (price + premium) always comes back first; then the loss is paid from","order":[{"n":1,"from":"the member’s deposit on this job","bound":"the deposit","why":"the seller’s own stake goes first"},{"n":2,"from":"the partner’s bond","bound":"what this job reserved","why":"the partner vouched for the member: its money goes before anyone else’s"},{"n":3,"from":"an outside pool on the member","bound":"the job’s coverCap","why":"backers pay only what the seller and its partner could not"}],"attacks":[{"attack":"a partner (or its member) buys from its own member, provokes a finding, and drains outside backers","closed_by":"bond before pool: the partner’s own money pays first; and the buyer may not be the member or the partner owner (refused at funding)"},{"attack":"a buyer, the seller and an arbitrator collude on a false finding","closed_by":"a finding needs a registered arbitrator (the hook’s role, behind the Safe and timelock); what is paid is bounded by deposit + reservation + coverCap, all fixed at funding"},{"attack":"the partner withdraws the bond while jobs are open, then a finding comes","closed_by":"only bond − capSum is withdrawable, after a 7-day notice, checked again at withdrawal; caps stay behind open reservations"},{"attack":"a reverting or malicious bond contract holds escrow hostage","closed_by":"the bond is fixed in the hook (one address, set by governance); release is called in try/catch; a failed slash never makes the pool pay the bond’s share"},{"attack":"a partner over-promises: caps larger than the bond","closed_by":"capSum ≤ bond at every step (mint, setCap), and reserved ≤ cap per member"},{"attack":"a retired or unknown member is named on a new job","closed_by":"reserve refuses an inactive or unknown member; the accept reverts and nothing is held"},{"attack":"a cross-asset bond pays in another token at a stale price","closed_by":"the bond asset must equal the job token, or the job is refused"},{"attack":"reentrancy through the token or the bond","closed_by":"nonReentrant on every money path, fixed ASSET and HOOK, SafeERC20, exact pulls (TransferShort)"}],"tests":"protocols/ours/partner-bond/test (27: invariants, gates) and protocols/ours/assurance-v4/test/PartnerBondWiring.t.sol (27: the payout bands and a fuzz of the order at every loss, each attack above as a named test)","wiring":{"open":"openJobWithBond(salt, terms, partnerNs)","events":["CheatPaidFrom(jobId, fromDeposit, fromBond, fromPool)","BondReleaseFailed(jobId, bond)","PartnerBondSet(bond)"],"setter":"setPartnerBond(bond) — owner only; zero turns new bond jobs off","storage":"partnerBond, bondOf[jobId] (appended; __gap 35 → 33)"},"devnet":{"file":"protocols/ours/assurance-v4/devnet/deployments/36927-v4-partner-bond.json","hook":"0xd65d173cd4f83Dc1663C845269E57703FaC61979","bond":"0x36E030CAC080DF6B62f45Ea3f20BE06758a5Da03","cheat_tx":"0x1ba333a69493d30ea2dff4eac874d4ec13f4c38cca1a598b9f614d8c04212de3","paid":"loss 25 dUSDC: escrow 10.1 back, then 12 from the deposit, 10 from the bond, 3 from the pool"}}},"source":"the `partner` section of the coordination machine (schemas/transitions.json), vendored byte for byte and copied here as it stands","get_key":{"free":true,"why":"a free key in one call (no payment, nothing signed): send it as X-API-Key for its own budget on /v1/demands, /gw/rpc, /gw/mcp and /v1/agents/register","http":{"method":"POST","url":"https://coord.taifoon.dev/v1/register","body":{},"optional":{"wallet_address":"0x… a label for your tenant; nothing is signed"}},"mcp":{"server":"https://coord.taifoon.dev/mcp","tool":"taifoon_register","arguments":{}},"cli":"npx @taifoon/cli login --free","quickstart":"https://coord.taifoon.dev/v1/quickstart"}}