feat: add NEAR Protocol chain support (Ed25519 + Borsh + NEP-413)#221
Conversation
|
@0xultravioleta is attempting to deploy a commit to the MoonPay Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9a48ecd. Configure here.
9a48ecd to
9c93175
Compare
njdawn
left a comment
There was a problem hiding this comment.
everything looks good! some ci issues to fix, then can merge
Closes open-wallet-standard#219 Adds NEAR Protocol as a supported chain in OWS. Ed25519 over canonical Borsh transactions, with SHA-256 pre-hash before signing. Genesis-agnostic signer (network binding lives in Transaction.block_hash, same model Algorand uses for `gh`). ## Chain spec ChainType: Near CAIP-2: near:mainnet, near:testnet BIP-44 coin type: 397 (SLIP-44) Curve: Ed25519 (existing Curve::Ed25519) Default derivation path: m/44'/397'/{index}' (NEAR Foundation, hardened) Address (implicit): 64-char lowercase hex of the ed25519 pubkey TX serialization: Borsh TX signing preimage: Ed25519(SHA-256(borsh(Transaction))) encode_signed_transaction: borsh(Transaction) || 0x00 || sig64 signMessage (V1): raw ed25519 over message bytes (parity with Solana). NEP-413 follow-up tracked. Default RPC: https://rpc.{mainnet,testnet}.near.org ## Files (23 changed, +702 / -50) Core implementation: - ows-signer/src/chains/near.rs (new) — NearSigner, 15 inline tests - ows-signer/src/chains/mod.rs — module + factory registration - ows-core/src/chain.rs — ChainType::Near, KNOWN_CHAINS rows, namespace, coin_type, FromStr, Display, serde tests, parse_chain test. Also fixes pre-existing Spark omission from ALL_CHAIN_TYPES (was [10] now [12]). - ows-core/src/config.rs — default RPC URLs - ows-lib/src/near_rpc.rs (new) — broadcast_tx_commit JSON-RPC helper. Sanitized error messages: never embeds raw RPC response payload in Display output (would leak operational data — tx details, account identifiers — into logs/UI). - ows-lib/src/lib.rs, ops.rs — module export, broadcast dispatch, 3 integration-test loops include "near". - ows-pay/src/discovery.rs — format_near() with yoctoNEAR divisor (10^24 = 1 NEAR), wired into format_price(). 6 unit tests. Bindings tests: - bindings/node/__test__/index.spec.mjs — "near" in chain coverage, account count 10 -> 12 (Spark omission fix), spark:* assertion. - bindings/python/tests/test_bindings.py — same pattern, also fixes pre-existing missing "xrpl" in derive-all loop. Documentation (DUAL — docs/ AND website-docs/md/ kept in sync): - 07-supported-chains.md: chain families table, non-EVM networks, shorthand aliases, HD derivation tree. - 02-signing-interface.md: chain-specific signMessage / signTransaction semantics for NEAR. README normalization: - ows/README.md, bindings/{node,python}/README.md, readme/templates/{ows,node,python}.md, readme/partials/{supported-chains,why-ows}.md, skills/ows/SKILL.md — chain enumerations now consistently include Spark, Nano, NEAR (also fixed pre-existing Nano omissions). ## Why these design choices - No new dependencies. ed25519-dalek, sha2, hex, bs58, base64 are already in the workspace. We do NOT pull near-primitives (heavy: tokio + dozens of transitive crates). NearSigner is ~330 LoC. - Implicit account, not named. derive_address returns the implicit account ID (hex(pubkey)). Named accounts (alice.near) require on-chain registration and are out of scope for a stateless signer. - HD derivation reuses existing SLIP-10. NEAR's hardened-only path works through the existing ed25519 SLIP-10 implementation in hd.rs. No new HD machinery (in contrast to Algorand PR open-wallet-standard#124 which needed BIP32-Ed25519 with Peikert). - Stateless signing. block_hash inside the Transaction binds it to a network, so the signer doesn't need a network parameter at signing time. Same model Algorand PR open-wallet-standard#124 uses. ## Production reference A production x402 facilitator with full NEAR support runs at facilitator.ultravioletadao.xyz, source at UltravioletaDAO/x402-rs (Rust, ~600 LoC NEAR implementation). It uses NEP-366 SignedDelegateAction for gasless meta-transactions on the facilitator side; this PR provides the underlying signer primitives. Meta-tx wrapping belongs in ows-pay as a follow-up. ## Test strategy - 15 unit tests inline in chains/near.rs covering trait properties, derivation, RFC 8032 vector 1 implicit address, sign/verify roundtrip, determinism, sign_message parity, invalid key length, sha256 prehash semantics, encode_signed layout, sig length rejection, empty input rejection, full extract -> sign -> encode pipeline. - Byte-parity test against near-api-js test/unit/transactions/data/ transaction1.json: hard-coded canonical Transaction borsh hex (transfer test.near -> whatever.near, nonce=1). Verifies extract_signable_bytes is identity AND encode_signed_transaction emits exactly tx_bytes || 0x00 || sig64 matching near-api-js's borsh(SignedTransaction) layout. - 6 format_near unit tests: whole / fractional / zero / one yoctoNEAR / typical NEP-141 storage deposit / non-numeric input. - 3 ops.rs integration loops include "near" (derive_address_all_chains, mnemonic_wallet_sign_message_all_chains, mnemonic_wallet_sign_tx_all_chains). CI gates verified locally: cargo fmt --all --check clean cargo clippy --workspace --all-targets -- -D warnings clean cargo test --workspace 604 passed .githooks/pre-commit pass ## Out of scope (tracked as follow-ups) - NEP-413 prefixed message signing (V1 sign_message is raw ed25519 for parity with Solana; NEP-413 is a structurally distinct flow). - NEP-366 SignedDelegateAction (belongs in ows-pay, not the signer). - Named-account resolution (alice.near -> AccountId requires RPC). - Live testnet broadcast smoke test (Nano open-wallet-standard#109 didn't include one either; can be added behind #[ignore] later). References: - near-api-js: https://github.com/near/near-api-js - NEP-413: https://github.com/near/NEPs/blob/master/neps/nep-0413.md - NEP-366: https://github.com/near/NEPs/blob/master/neps/nep-0366.md - SLIP-44 coin type 397: https://github.com/satoshilabs/slips/blob/master/slip-0044.md - Nano PR open-wallet-standard#109 (merged) — closest structural template - Algorand PR open-wallet-standard#124 (open) — sibling ed25519 chain addition Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9c93175 to
16c6caf
Compare
|
Thanks for the review @njdawn! CI is now green on The failure was the All 9 checks now pass on the new head:
PR is still a single squashed commit. Ready to merge when you are. |

Closes #219
Summary
Adds NEAR Protocol as a supported chain in OWS, following the canonical pattern from merged Nano PR #109 and in-flight Algorand PR #124. NEAR uses Ed25519 (no new curve) over Borsh-serialized transactions, with SHA-256 pre-hash before signing. The signer is genesis-agnostic — network binding lives inside
Transaction.block_hash, the same model Algorand uses forgh.Chain Specification
ChainTypeNearnear:mainnet,near:testnetCurve::Ed25519)m/44'/397'/{index}'(NEAR Foundation / Sender Wallet, fully hardened)Ed25519(SHA-256(borsh(Transaction)))encode_signed_transactionlayoutborsh(Transaction) ‖ 0x00 ‖ sig64block_hashfield within Transaction (genesis-agnostic signer)signMessage(V1)https://rpc.mainnet.near.org,https://rpc.testnet.near.orgFiles Changed (31 files, +696 / -49)
Core implementation
ows/crates/ows-signer/src/chains/near.rs(new) —NearSigner, 15 inline testsows/crates/ows-signer/src/chains/mod.rs— module + factory registrationows/crates/ows-core/src/chain.rs—ChainType::Nearvariant + KNOWN_CHAINS rows + namespace + coin_type + serde + parse_chain testows/crates/ows-core/src/config.rs— default RPC URLsows/crates/ows-lib/src/near_rpc.rs(new) —broadcast_tx_commitJSON-RPC helperows/crates/ows-lib/src/lib.rs,ops.rs— module export + broadcast dispatch + 3 integration-test loops includenearows/crates/ows-pay/src/discovery.rs—format_near()with yoctoNEAR divisor (10^24), wired intoformat_price(), 6 unit testsBindings tests
bindings/node/__test__/index.spec.mjs—nearadded to chain coverage loops; account count 10 → 12 (also fixed pre-existing Spark omission);spark:startswith assertion addedbindings/python/tests/test_bindings.py— same pattern; also fixed pre-existing missingxrplin derive-all loopDocumentation
docs/07-supported-chains.md+website-docs/md/07-supported-chains.md— chain families table, non-EVM networks, shorthand aliases, HD derivation treedocs/02-signing-interface.md+website-docs/md/02-signing-interface.md— chain-specific signMessage / signTransaction semantics for NEARREADME normalization (drive-by — fixed pre-existing gaps too)
ows/README.md,bindings/{node,python}/README.md,readme/templates/{ows,node,python}.md,readme/partials/{supported-chains,why-ows}.md,skills/ows/SKILL.md— chain enumerations now consistently include Spark, Nano, NEARPre-existing fix (drive-by, single-line)
ows-core/src/chain.rs:ALL_CHAIN_TYPES: [ChainType; 10]→[ChainType; 12]— Spark was already aChainTypevariant with a registered signer but missing from the universal-wallet derivation list. Fixed alongside NEAR addition since adding NEAR exposed the count mismatch in bindings tests. Easy to revert if maintainers prefer this in a separate PR.Why these design choices
ed25519-dalek,sha2,hex,bs58,base64are all already in the workspace. We do not pullnear-primitives(heavy: tokio + dozens of transitive crates the wallet doesn't need). The minimal NearSigner is ~330 LoC.derive_addressreturns the implicit account ID (hex(pubkey)). Named accounts (alice.near) require on-chain registration and are out of scope for a stateless signer primitive.m/44'/397'/{index}'works through the existing ed25519 SLIP-10 implementation inhd.rs. No new HD machinery (in contrast to Algorand PR Feat: add algorand chain and AVM chain type support #124 which needed BIP32-Ed25519 with Peikert).block_hashinside the Transaction binds it to a network, so the signer doesn't need anetworkparameter at signing time. Same model Algorand PR Feat: add algorand chain and AVM chain type support #124 uses.Production Reference
A production x402 facilitator with full NEAR support runs at
facilitator.ultravioletadao.xyz, source atUltravioletaDAO/x402-rs. It uses NEP-366 SignedDelegateAction for gasless meta-transactions on the facilitator side; this PR provides the underlying signer primitives so OWS wallets can produce the bytes that facilitator settles. Meta-tx wrapping belongs inows-payas a follow-up.Test Strategy
Unit tests (15 inline in
chains/near.rs)hex(pubkey)lowercase produces the canonical 64-char NEAR implicit account ID)ed25519-dalek::Verifiersign_messageparity withsign(V1)sign_transactionMUST sha256 the input first (verified by signing both ways and asserting the signatures differ; then verifying thesign_transactionoutput matchesEd25519(SHA-256(tx_bytes)))encode_signed_transactionbyte layout (1-byte ED25519 enum tag + 64-byte sig)extract → sign → encodewith signature verifiable againstsha256(tx_bytes)Byte-parity test against
near-api-jstest_borsh_byte_parity_with_near_api_js_transaction1hard-codes the canonicalTransactionborsh hex fromnear/near-api-js test/unit/transactions/data/transaction1.json(transfer of 1 yoctoNEARtest.near→whatever.near, nonce=1). Verifies thatextract_signable_bytesis identity AND thatencode_signed_transactionemits exactlytx_bytes ‖ 0x00 ‖ sig64matchingnear-api-js'sborsh(SignedTransaction)layout.Integration tests in
ops.rsNEAR added to all 3 cross-chain loops:
derive_address_all_chainsmnemonic_wallet_sign_message_all_chainsmnemonic_wallet_sign_tx_all_chainsformat_nearunit tests (6)Whole NEAR / fractional / zero / one yoctoNEAR / typical NEP-141 storage deposit / non-numeric input rejection.
CI gates (verified locally)
Out of scope (tracked as follow-ups)
tag 2147484061 ‖ borsh({message, nonce, recipient, callbackUrl?})) — the V1sign_messagehere is raw ed25519 over message bytes for parity with Solana. NEP-413 is a structurally distinct flow with required fields; better as an opt-in API in a follow-up.ows-pay, not the signer crate.alice.near→ AccountId) — requires RPC, not a pure stateless signer concern.#[ignore]later.References
cc @njdawn
Note
Medium Risk
Adds a new chain implementation and RPC broadcasting path, which touches signing/encoding and expands universal wallet derivation; mistakes could cause invalid signatures/tx bytes or mis-broadcasts for NEAR.
Overview
Adds NEAR Protocol support end-to-end: introduces
ChainType::Nearwithnear:mainnet/near:testnet, default coin type/derivation path, and a newNearSignerthat signs transactions asEd25519(SHA-256(borsh(Transaction)))and encodes signed tx bytes asborsh(Transaction) || 0x00 || sig64.Wires NEAR into runtime flows by adding default NEAR RPC URLs, adding a
curl-basedbroadcast_tx_commitimplementation forsignAndSend/broadcast dispatch, and updatingows-payprice formatting to render yoctoNEAR amounts.Updates Node/Python bindings tests and universal-wallet expectations (account count
10 → 12, addsnearcoverage and aspark:assertion), and refreshes specs/READMEs to list NEAR and document its message/transaction signing conventions and aliases.Reviewed by Cursor Bugbot for commit 16c6caf. Bugbot is set up for automated code reviews on this repo. Configure here.