Conversation
Adds a full-access key to the signing account, modelled on account.delete. Needed to install an MPC-derived key for DAO-governed signing (ENG-718). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
`GetResult.receipts` lists every receipt with its executor, status, logs and return value, so a value an inner receipt returned (e.g. an MPC signature inside a DAO `act_proposal` chain, ENG-718) is reachable through the gateway. Asserting on it exposed that `mock-receiver` never worked: its `#[near]` parameters were underscore-prefixed, which is the JSON field name the wrapper deserializes, so every `*_on_transfer` panicked and the token contract refunded in full while the transaction still reported success. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
…ture/eng-720-gateway-txget-returns-per-receipt-outcomes' into feature/eng-718-tmplrmgr-dao-governed-mpc-signing-via-v1signer
`tmplrmgr mpc {derive-key, propose, show, relay}` has a Sputnik DAO ask the
NEAR MPC signer to sign a planned transaction, with the proposal as the single
source of truth: its `sign` action carries the hash and, unless `--blind`, its
description carries the exact bytes that hash to it. Either can be reviewed and
relayed later by anyone from `--dao --proposal-id` (plus the payload file for a
blind proposal), so nothing depends on the proposer's session.
Two payload kinds: a NEP-366 delegate action (default; expires at a block
height, relayed via tx.relaySignedDelegateAction) and a plain transaction
(near-cli compatible; bound to a block hash the CLI tracks). The signature is
read from the executing transaction's receipts and verified against the derived
key before relay, which also covers meta-transaction-executed proposals.
Also `tmplrmgr account add-key` (literal or MPC-derived key) and
`Network::mpc_contract_id`. Sandbox e2e runs against `mock-signer` and
`mock-dao`, new mocks of the two third-party contracts.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueComment |
…ey (ENG-719) `addKey` carries the same `AccessKeyPermission` `getAccessKey` reports, so function-call keys (receiver, method names, allowance) are installable, not only full access. `deleteKey` is the inverse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
…re/eng-718-tmplrmgr-dao-governed-mpc-signing-via-v1signer
…G-718) `--receiver-id [--method-name …] [--allowance]` installs a function-call key; omitted, the key is full access as before. `delete-key` is the inverse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
Round-trip both permission variants in the unit test, move the receiver id into near-api's String instead of formatting it, and update the docs the new methods made stale: `ActionInput`'s allowlist rationale and the permission enum's read-only wording. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
…re/eng-718-tmplrmgr-dao-governed-mpc-signing-via-v1signer
…718) `account add-key --key` is a plain gateway write again; the derived-key install moves to `mpc install-key`, which reuses the `--dao/--path` args `derive-key` and `propose` already take. The old form duplicated the default-path convention, accepted `--mpc-contract` it then ignored, and could install a function-call MPC key that `propose` rejects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
…gr-dao-governed-mpc-signing-via-v1signer
…gr-dao-governed-mpc-signing-via-v1signer
…ind (ENG-718) Review findings on #633: - `mpc relay` (delegate action, needs a fee payer) and `mpc broadcast` (transaction, needs nobody; `--print` emits the signed bytes) replace the single command that demanded relayer credentials for both. - The envelope carries bytes only: a transaction's expiry is derived from the on-chain height of the block hash inside the signed bytes, not from unsigned metadata that could be edited past the hash check. - `show` reports the `sign` call's deposit and gas and refuses a deposit other than the 1 yoctoNEAR tmplrmgr attaches. - Among several `sign` receipts in one execution, the signature that verifies over this payload is chosen; `--nonce` below the key's next nonce is refused; a payload variant must match its signing domain; a description may carry exactly one envelope marker; the payload file is written through a fresh sibling temp file. - E2E covers pre-vote review, decoy payloads on show/relay/broadcast, the wrong send command, a rejected nonce, and an expired approved payload. - Docs: the Chain Signatures trust boundary now describes the derived-key, DAO-approved path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes ENG-718. Builds on #631 (ENG-719) and #632 (ENG-720), both merged; the diff is the tmplrmgr work alone.
Let a Sputnik DAO authorize transactions on accounts it controls through an MPC-derived full-access key (
v1.signer/v1.signer-prod.testnet), without near-cli's "keep the terminal open until the vote passes" flow.Why not near-cli-rs
sign-with-mpc … submit-mpc-as-dao-proposalholds the inner unsigned tx (nonce, block hash) only in process memory, never prints it, and blocks on an interactive prompt until the proposal is executed. Ctrl-C loses the tx;sign-later/--offlineare not offered on that path;sign-transaction <base64>drops nonce/block_hash. No upstream fix (submit-mpc-as-dao-proposal: cannot recover the MPC signature when the DAO proposal was executed via a meta-transaction (Delegate action) near/near-cli-rs#669 is the meta-tx recovery bug).transaction_validity_period = 86400blocks ≈ 15 h on mainnet; our DAO'sproposal_periodis 7 days.respond: the signature exists only as theSuccessValueof thev1.signerself-receipt in the receipt chain of whichever tx executed the proposal.Design
The DAO proposal is the single source of truth. Its
signaction carries the hash (payload_v2); the unsigned bytes that hash to it live in the proposal description (tmplrmgr-mpc:{…}line) or, with--blind --out <file>, only in a local payload file. Same envelope JSON either way, so the two are interchangeable forshow/relay --payload-file.Two payload kinds behind one
SignablePayloadenum:delegate-action(default): NEP-461 hash of aDelegateAction { sender_id, nonce, max_block_height, actions, public_key: derived }. No block-hash expiry; relayed by any funded account viatx.relaySignedDelegateAction.transaction: plainTransaction::V0hash (near-cli compatible); expiry is the on-chain height of its block hash plus the 86400-block window, refused before broadcast.broadcast --printemits the signed tx as base64 borsh fornear transaction send-signed-transaction.Commands
tmplrmgr mpc derive-key --dao --account-id [--path]→derived_public_keyview.tmplrmgr mpc install-key --dao [--path] <SignerArgs>— derives the key and adds it to the signer with full access (account.addKey).tmplrmgr mpc propose --plan <--print json output> --dao [--path] [--kind] [--nonce] [--valid-for-blocks] [--blind] [--out] <proposer SignerArgs>— checks the derived key is a full-access key on the controlled account, builds the payload, writes--outfirst (temp+rename), submitsadd_proposalwith deposit =policy.proposal_bond. Honours--print.tmplrmgr mpc show --dao --proposal-id [--payload-file]— decodes the payload, verifieshash(bytes) == payload_v2, that the payload's key is the one the DAO derives at the proposal's path, and that thesigncall attaches exactly 1 yoctoNEAR; prints actions, deposit/gas, status, expiry. Exits non-zero on any mismatch.tmplrmgr mpc relay … <relayer SignerArgs>(delegate action) andtmplrmgr mpc broadcast … [--print](transaction; no credentials) — both take--dao --proposal-id --tx-hash --tx-signer [--payload-file], run the same checks, requireApprovedand unexpired, read the MPC receipts viatx.get, and pick the signature that verifies against the derived key + hash (so meta-transaction- and multi-proposal executions work).--tx-signeris needed because NEAR resolves a transaction by hash and sender. The wrong command for a payload kind is an explicit error.tmplrmgr account add-key --key [--receiver-id [--method-name …] [--allowance]]overaccount.addKey(full access unless--receiver-id), andtmplrmgr account delete-key --keyoveraccount.deleteKey. Both are plain gateway writes with no MPC coupling.Network::mpc_contract_id()intemplar-gateway-client.Tests
signdeposit; payload build/decode/hash for both kinds, tx hash cross-checked againstnear-primitives;SignatureResponseparsing (ed25519 + secp256k1), wrong-key rejection; DAO proposal parsing from a real mainnetget_proposalshape; clap matrix (--blindrequires--out,add-keypermission flags,install-keydefault path).requires_sandbox_mpc_proposal_is_relayed_end_to_end, rstest cases public delegate action and blind transaction) against two new mocks,mock-signerandmock-dao: install-key → add-key/delete-key of a function-call key → a rejected--nonce→ propose → pre-voteshow→ a decoy payload refused byshow→ the test signs the hash the proposal carries as the "MPC" →act_proposal→ show → the wrong send command refused → the decoy refused by relay/broadcast → relay/broadcast → the controlled account's transfer lands. The blind case also assertsshowfails without--payload-file. A second test (requires_sandbox_mpc_expired_payload_is_refused_before_broadcast) approves a short-lived delegate action, fast-forwards past it, and assertsrelayrefuses it.Not yet done
v1.signer-prod.testnetwith a testnet Sputnik DAO (the only place the real yield/resume + Sputnik callback chain is exercised).Docs
docs/src/architecture.md(Chain Signatures trust row) anddocs/src/governance.md(new DAO-approved MPC signing section) describe the derived-key path this adds.Verification
just test-fast -p templar-manager -p templar-gateway-client -p templar-contract-artifacts -p templar-gateway-testing(475 passed)just test-sandbox -p templar-manager -E 'test(requires_sandbox_mpc)'cargo fmt --all --check;cargo clippy --workspace --all-features --tests -- -D warnings🤖 Generated with Claude Code
https://claude.ai/code/session_01F5amj1VuS6TPoAyxeFroV2
This change is