feat: gas-key support (nearcore 2.13)#607
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates near-cli-rs for nearcore 2.13 / protocol v85 by adding first-class support for new protocol features: gas keys (including creation and nonce inspection) and post-quantum ML-DSA-65 key generation, alongside a set of required primitives/query adaptations and build pipeline tweaks.
Changes:
- Add gas-key CLI flows: create gas-key permissions in
account add-key, fund/withdraw gas-key balances, and query/print parallel nonces viaViewGasKeyNonces. - Add signature-scheme plumbing (Ed25519 vs ML-DSA-65), including a new offline
generate-keypaircommand and updated autogeneration flows. - Bump nearcore-related dependencies to 0.37.0-rc.2 / matching git revisions, and update query/action handling for nearcore 2.13 changes (DelegateV2, ViewState after_key/limit, etc.).
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/transaction_signature_options/sign_with_mpc/mod.rs | Reject ML-DSA-65 for MPC signing; document/guard unsupported key type. |
| src/common.rs | Introduce SignatureScheme + GeneratedKeyPair, gas-key nonce query plumbing, updated error mappings, and ViewState arg updates. |
| src/commands/transaction/reconstruct_transaction/mod.rs | Explicitly reject reconstructing DelegateV2 transactions. |
| src/commands/mod.rs | Register new top-level generate-keypair command module/variant. |
| src/commands/generate_keypair/mod.rs | New offline keypair generation command (Ed25519 / ML-DSA-65) with print/save modes and tests. |
| src/commands/contract/view_storage/output_format/mod.rs | Adapt ViewState query to include after_key/limit. |
| src/commands/account/withdraw_from_gas_key/mod.rs | New command to withdraw from a gas key balance back to the owning account. |
| src/commands/account/view_gas_key_nonces/mod.rs | New command to query and display per-index gas-key nonces. |
| src/commands/account/mod.rs | Wire new gas-key account subcommands into the account command tree. |
| src/commands/account/fund_gas_key/mod.rs | New command to transfer funds into a gas key balance. |
| src/commands/account/delete_key/public_keys_to_delete.rs | Adapt delete-key picker to PublicKeyHandle and skip ML-DSA-65 entries lacking full pubkeys. |
| src/commands/account/create_account/sponsor_by_faucet_service/add_key/autogenerate_new_keypair/mod.rs | Add --signature-scheme to autogenerate flow and use GeneratedKeyPair for output/save. |
| src/commands/account/create_account/fund_myself_create_account/add_key/autogenerate_new_keypair/mod.rs | Same signature-scheme + GeneratedKeyPair adaptation for fund-myself flow. |
| src/commands/account/add_key/mod.rs | Add gas-key permission variants to account add-key. |
| src/commands/account/add_key/autogenerate_new_keypair/save_keypair_to_legacy_keychain/mod.rs | Persist generated keys via GeneratedKeyPair (supports ML-DSA-65 minimal JSON). |
| src/commands/account/add_key/autogenerate_new_keypair/save_keypair_to_keychain/mod.rs | Same as above for keychain save path. |
| src/commands/account/add_key/autogenerate_new_keypair/print_keypair_to_terminal/mod.rs | Print generated key info via GeneratedKeyPair::terminal_info(). |
| src/commands/account/add_key/autogenerate_new_keypair/mod.rs | Add --signature-scheme to add-key autogeneration flow. |
| src/commands/account/add_key/access_key_type/mod.rs | Add gas-key access-key permission builders and nonce-count validation. |
| Cargo.toml | Bump/pin nearcore 2.13-related dependencies (rc + git revisions). |
| Cargo.lock | Lockfile updates for new dependency graph (incl. aws-lc-sys-related additions). |
| .github/workflows/release.yml | Set AWS_LC_SYS_PREBUILT_NASM=1 to support Windows builds with aws-lc-sys. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8089185e20
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Add the first increment of nearcore 2.13 gas-key support: - account add-key: new grant-gas-key-full-access and grant-gas-key-function-call permission variants, building an AddKey action with AccessKeyPermission::GasKeyFullAccess / GasKeyFunctionCall (balance + parallel-nonce count via GasKeyInfo). - account view-gas-key-nonces: issues the view_gas_key_nonces query and prints the per-index nonces from GasKeyNoncesView.
Add the funding side of gas-key support:
- account fund-gas-key: builds Action::TransferToGasKey
(TransferToGasKeyAction { public_key, deposit }) to move NEAR into a
gas key's balance.
- account withdraw-from-gas-key: builds Action::WithdrawFromGasKey
(WithdrawFromGasKeyAction { public_key, amount }) to move NEAR from a
gas key's balance back to the owning account.
Both are plain action-builders (owner is signer == receiver) routed
through the normal ActionContext / signing flow, mirroring tokens
send-near. No nonce/signing-layer changes.
The protocol rejects an AddKey that sets a non-zero gas-key balance
("Adding a gas key with non-zero balance is not allowed"), so the
--balance arg on grant-gas-key-full-access / grant-gas-key-function-call
was a footgun. Construct GasKeyInfo with balance 0 unconditionally and
drop the balance input; gas keys are funded after creation via
fund-gas-key.
near-jsonrpc-client =0.22.0-rc.1, near-socialdb-client =0.16.0-rc.1, nearcore =0.37.0-rc.2 (drop git-rev pins)
The keychain entry name and legacy-keychain file name were the full ~1952-byte ML-DSA-65 public key. That overflows filesystem name limits (File name too long) and never matches the ml-dsa-65-hash:... handle the RPC access-key list reports, so a saved key could not be found again for signing. Save autogenerated keys under GeneratedKeyPair::keychain_key_id() - the full key for ed25519 (unchanged), the on-trie handle for ML-DSA-65 - which is exactly what sign_with_keychain / the legacy signer look up by.
"fetch query GasKeyNonces" read like an internal type; say "fetch the gas key nonces" instead.
…n_id The old comment implied the only caller was the signing-payload match. near_key_type_to_mpc_domain_id is also called by derive_public_key; note that both paths exclude ML-DSA-65 (the payload match errors on it, and derive_public_key only receives a key type chosen from MpcKeyType, which offers ed25519 / secp256k1 only).
Every near-* crate is now on an exact `=` 2.13 pre-release pin with no git deps; state that instead of the earlier mixed git/registry wording.
The prompt hardcoded 1..=1024; format it from AccessKeyPermission::MAX_NONCES_FOR_GAS_KEY (the same constant validation uses) so it stays correct if the protocol limit changes.
The protocol rejects an AddKey that sets an allowance on a gas-key function-call key (GasKeyFunctionCallAllowanceNotAllowed); a gas key pays gas from its own balance. Drop the --allowance arg/prompt for grant-gas-key-function-call and always build the permission with allowance: None (receiver_id / method_names still apply).
## Cause
A gas key (nearcore 2.13 / protocol v85) pays for transactions from its
own
balance and uses N parallel nonces. Signing a transaction with one
requires a
versioned (V1) transaction whose nonce is `TransactionNonce::GasKeyNonce
{ nonce,
nonce_index }`; near-cli only ever builds the legacy (V0)
`TransactionNonce::Nonce`
transaction, so a gas key can be created/funded but not used to sign.
## Fix
A single shared nonce resolver in `transaction_signature_options` that
all six
`sign_with_*` modules call, keeping the gas-key + tx-version logic in
one place:
- `resolve_online_nonce()` queries the access key; an ordinary key
resolves to
`nonce + 1` and a V0 transaction (unchanged behavior). A gas key
additionally
queries `view_gas_key_nonces` and resolves to `nonces[nonce_index] + 1`.
- `build_unsigned_transaction()` wraps the
(post-`on_before_signing_callback`)
`TransactionV0` into a V0 transaction for an ordinary nonce, or a V1
transaction with `GasKeyNonce` (+ default `NonceMode`) for a gas key.
Each signer gains an optional `--nonce-index <N>` (default 0; `u64`
narrowed to
`NonceIndex` with a bound check, per the interactive-clap `ToCli`
constraint) and
otherwise just calls the resolver and the builder — the gas-key path is
invisible
to the six modules.
## Verification
The signing path can't be unit-tested against RPC, so compile + clippy
is the CI
gate here (`cargo check --tests --no-default-features` [+`--features
ledger`],
`cargo fmt --all --check`, `cargo clippy -- -D warnings` all pass).
Manual
sandbox/testnet verification of an end-to-end gas-key-signed transaction
is
recommended before this leaves draft.
Stacked on #607 (fund/withdraw). Draft.
## 🤖 New release
* `near-cli-rs`: 0.27.0 -> 0.28.0 (⚠ API breaking changes)
### ⚠ `near-cli-rs` breaking changes
```text
--- failure constructible_struct_adds_field: externally-constructible struct adds field ---
Description:
A pub struct constructible with a struct literal has a new pub field. Existing struct literals must be updated to include the new field.
ref: https://doc.rust-lang.org/reference/expressions/struct-expr.html
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/constructible_struct_adds_field.ron
Failed in:
field CliSignPrivateKey.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_private_key/mod.rs:5
field InteractiveClapContextScopeForGenerateKeypair.signature_scheme in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/create_account/sponsor_by_faucet_service/add_key/autogenerate_new_keypair/mod.rs:3
field CliSignLedger.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_ledger/mod.rs:18
field CliSignLegacyKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_legacy_keychain/mod.rs:12
field CliSignKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_keychain/mod.rs:9
field SignLedgerContext.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_ledger/mod.rs:51
field InteractiveClapContextScopeForSignSeedPhrase.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_seed_phrase/mod.rs:7
field CliSignAccessKeyFile.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_access_key_file/mod.rs:5
field CliGenerateKeypair.signature_scheme in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/create_account/sponsor_by_faucet_service/add_key/autogenerate_new_keypair/mod.rs:3
field InteractiveClapContextScopeForSignPrivateKey.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_private_key/mod.rs:5
field InteractiveClapContextScopeForSignLedger.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_ledger/mod.rs:18
field InteractiveClapContextScopeForSignLegacyKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_legacy_keychain/mod.rs:12
field InteractiveClapContextScopeForSignKeychain.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_keychain/mod.rs:9
field InteractiveClapContextScopeForSignAccessKeyFile.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_access_key_file/mod.rs:5
field CliSignSeedPhrase.nonce_index in /tmp/.tmpat9wFR/near-cli-rs/src/transaction_signature_options/sign_with_seed_phrase/mod.rs:7
--- failure enum_no_repr_variant_discriminant_changed: enum variant had its discriminant change value ---
Description:
The enum's variant had its discriminant value change. This breaks downstream code that used its value via a numeric cast like `as isize`.
ref: https://doc.rust-lang.org/reference/items/enumerations.html#assigning-discriminant-values
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/enum_no_repr_variant_discriminant_changed.ron
Failed in:
variant TopLevelCommandDiscriminants::Extensions 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:60
variant TopLevelCommandDiscriminants::Extensions 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:60
variant AccountActionsDiscriminants::GetPublicKey 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:69
variant AccountActionsDiscriminants::AddKey 8 -> 9 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:74
variant AccountActionsDiscriminants::DeleteKeys 9 -> 12 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:89
variant AccountActionsDiscriminants::ManageStorageDeposit 10 -> 13 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:94
variant AccountActionsDiscriminants::GetPublicKey 7 -> 8 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:69
variant AccountActionsDiscriminants::AddKey 8 -> 9 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:74
variant AccountActionsDiscriminants::DeleteKeys 9 -> 12 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:89
variant AccountActionsDiscriminants::ManageStorageDeposit 10 -> 13 in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:94
--- failure enum_variant_added: enum variant added on exhaustive enum ---
Description:
A publicly-visible enum without #[non_exhaustive] has a new variant.
ref: https://doc.rust-lang.org/cargo/reference/semver.html#enum-variant-new
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/enum_variant_added.ron
Failed in:
variant AccountActionsDiscriminants:ViewGasKeyNonces in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:64
variant AccountActionsDiscriminants:FundGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:79
variant AccountActionsDiscriminants:WithdrawFromGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:84
variant AccountActionsDiscriminants:ViewGasKeyNonces in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:64
variant AccountActionsDiscriminants:FundGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:79
variant AccountActionsDiscriminants:WithdrawFromGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:84
variant CliTopLevelCommand:GenerateKeypair in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:16
variant CliAccountActions:ViewGasKeyNonces in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:27
variant CliAccountActions:FundGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:27
variant CliAccountActions:WithdrawFromGasKey in /tmp/.tmpat9wFR/near-cli-rs/src/commands/account/mod.rs:27
variant TopLevelCommandDiscriminants:GenerateKeypair in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:56
variant TopLevelCommandDiscriminants:GenerateKeypair in /tmp/.tmpat9wFR/near-cli-rs/src/commands/mod.rs:56
--- failure function_missing: pub fn removed or renamed ---
Description:
A publicly-visible function cannot be imported by its prior path. A `pub use` may have been removed, or the function itself may have been renamed or removed entirely.
ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/function_missing.ron
Failed in:
function near_cli_rs::transaction_signature_options::send::sleep_after_error, previously in file /tmp/.tmppWZUdR/near-cli-rs/src/transaction_signature_options/send/mod.rs:193
--- failure trait_method_added: pub trait method added ---
Description:
A non-sealed public trait added a new method without a default implementation, which breaks downstream implementations of the trait
ref: https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-item-no-default
impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.48.0/src/lints/trait_method_added.ron
Failed in:
trait method near_cli_rs::common::RpcQueryResponseExt::gas_key_nonces_view in file /tmp/.tmpat9wFR/near-cli-rs/src/common.rs:3499
```
<details><summary><i><b>Changelog</b></i></summary><p>
<blockquote>
##
[0.28.0](v0.27.0...v0.28.0)
- 2026-07-02
### Added
- sign transactions with a gas key (nonce_index)
([#608](#608))
- gas-key support (nearcore 2.13)
([#607](#607))
- post-quantum (ML-DSA-65) key generation (draft)
([#605](#605))
- Validate beneficiary account before deleting account
([#596](#596))
### Fixed
- Fixed output information for transaction status "Started"
([#594](#594))
### Other
- Fixed NPM Trusted Publishing
([#603](#603))
</blockquote>
</p></details>
---
This PR was generated with
[release-plz](https://github.com/release-plz/release-plz/).
---------
Co-authored-by: r-near <[email protected]>
Cause
nearcore 2.13 (protocol v85) introduces gas keys: an access-key permission that
carries its own NEAR balance and
Nparallel nonces. near-cli has no way tocreate one, fund it, or inspect its nonces.
Fix
Stacked on the nearcore-2.13 branch (#605):
account add-keygains two permission variants,grant-gas-key-full-accessandgrant-gas-key-function-call, that build anAddKeyaction withAccessKeyPermission::GasKeyFullAccess/GasKeyFunctionCall. Gas keys are created empty (the protocol rejects anAddKeywith a non-zero gas-key balance), so there is no balance argument;fund the key afterwards with
fund-gas-key. The function-call variant takes acontract id and method names but no allowance (the protocol rejects an
allowance on a gas-key function-call key). The parallel-nonce count is
collected as
u64(interactive-clap has noToCli for u16) and narrowed toNonceIndexwith a1..=MAX_NONCES_FOR_GAS_KEYbound check.account fund-gas-keytransfers NEAR from the account into a gaskey's balance.
account withdraw-from-gas-keymoves NEAR from a gas key'sbalance back to the account.
account view-gas-key-nonces <account> <public-key>issues theview_gas_key_noncesquery (via the genericQueryRequest::ViewGasKeyNoncespath) and prints the per-index nonces from
GasKeyNoncesView.Signing a transaction with a specific
GasKeyNonce(DelegateV2 / parallel-noncesigning) remains a follow-up increment.