NEAR's MPC allows a NEAR account to create derivative accounts (public keys) and signatures of transactions for other blockchains.
Several MPC nodes maintain a single public key. This key is combined with your NEAR accountId (unique) and a "path" offset (chosen by client). This produces a new and unique public key. The generation of signatures via the MPC nodes can only be authorized by same NEAR account by calling the sign
method of the MPC contract. This NEAR account can be a smart contract.
The creation of secp256k1 public keys and addresses for Bitcoin (like) and EVM chains is currently supported.
- Obtain the MPC public key (near view [MPC_CONTRACT_ID]
public_key
) and hardcode into.env
or code - Choose a path for the derived account (public key) see: Path naming conventions
- Use
./src/kdf.ts -> generateAddress
to generate the derived account address and public key - Use the
sign
method of./src/near.ts -> sign
which calls the MPC contract to sign payload (hash of TX) - Using a library (ethers/bitcoinjs-lib) combine the transaction and signature to create signed transaction
- Broadcast the transaction e.g.
sendRawTransaction
yarn
NEAR_ACCOUNT_ID="[NEAR_TESTNET_ACCOUNT]"
NEAR_PRIVATE_KEY="[NEAR_ACCOUNT_PRIVATE_KEY]"
MPC_PATH="[MPC_PATH]"
MPC_CHAIN="[ethereum|bitcoin]"
MPC_CONTRACT_ID="v1.signer-prod.testnet"
MPC_PUBLIC_KEY="secp256k1:4NfTiv3UsGahebgTaHyD9vF8KYKMBnfd6kh94mK6xv8fGBiJB8TBtFMP5WWXz6B89Ac1fbpzPwAvoyQebemHFwx3"
TATUM_API_KEY=""
The MPC contract for testnet may change from time to time. If you are having difficulties with the contract e.g. timeouts, signatures not being returned / ready, or any other issues with the contract address, reach out in the following Telegram group: https://t.me/chain_abstraction
The MPC public key corresponds to the MPC contract, but also may change from time to time as nodes are added and removed from the MPC network or the network is rebooted. Please verify the latest MPC public key for the contract you are using by using near-cli
and the following command:
near view v1.signer-prod.testnet public_key
- Read the
Installation
steps and set up all environment variables first with.env
file. - Use the commands to generate some Chain Signature addresses first.
- Fund these addresses with the Testnet Faucets provided in the links below.
- Use the commands to send funds and transactions from your generated addresses.
yarn start [commands]
- -ea - ethereum address (EVM)
- -ba - bitcoin testnet address
- -da - dogecoin testnet address
- -ra - ripple testnet address
- -oa - Oraichain's address
- -oea - Oraichain's Ethermint address
- -s - sign sample payload using NEAR account
- -etx - send ETH
- -btx - send BTC
- -dtx - send DOGE (requires API KEY)
- -rtx - send XRP
- -otx - send ORAI
- -oetx - send ORAI using Oraichain's EVM account (only works on Cosmos-based chains having the Ethermint module)
- --amount - amount to send (ETH or sats or ORAI)
- --to - destination address
Usage: yarn start [commands]
- -d, -edc - deploy contract
- --to - the contract address to view/call
- -v, -view - view contract state (readonly call)
- -c, -call - call contract method
- --path - path to EVM bytecode file from root of this project
- --method - name of method view/call
- --args - arguments e.g. '{"address":"0x525521d79134822a342d330bd91da67976569af1"}' in single quotes
- --ret - list of return parameter types (if any) e.g. ['uint256']
This script calls the MPC contract in the near.ts
file.
The basic arguments are:
request: {
payload: [u8; 32]
path: String,
key_version: u32,
}
The script also supports calling a proxy NEAR contract that will accept an EVM RLP encoded payload and hash this payload inside the contract.
After setting up all your environment variables ensure that your EVM account is funded.
The default network is Sepolia and given the MPC_PATH environment variable, you will receive a unique derived EVM address using the following command:
yarn start -ea
Fund this account before getting started to ensure it has enough gas to deploy an NFT contract and mint a few NFTs.
Start by deploying a new NFT contract:
yarn start -d
Check explorer link and make sure contract is deployed successfully.
Take your contract address from console result and call:
yarn start -c --to 0x[CONTRACT ADDRESS FROM STEP 1]
This will mint a token to default address 0x525521d79134822a342d330bd91da67976569af1
.
To mint a token to a different address use --args '{"address":"0x[SOME_OTHER_ADDRESS]"}'
with your args in single quotes and properly formatted JSON paths and values in double quotes.
View the balance of the default address using:
yarn start -v --to 0x[CONTRACT ADDRESS FROM STEP 1]
Which should output 1
the NFT balance of default address 0x525521d79134822a342d330bd91da67976569af1
To view the balance of a different address use --args '{"address":"0x[SOME_OTHER_ADDRESS]"}'
with your args in single quotes and properly formatted JSON paths and values in double quotes.
This example uses a NEAR contract (think of it like a proxy) to call the NEAR MPC Contract.
This produces a valid signature for a derived EVM account based on the "proxy" NEAR contract account ID, NOT the caller's NEAR account ID.
Our example will be a payable NEAR transaction that produces a valid signature to execute an EVM transaction.
- Client -> call
sign
method -> [NEAR CONTRACT] - [NEAR CONTRACT] -> [MPC Contract]
- [MPC Contract] -> return signature -> [NEAR CONTRACT]
- [NEAR CONTRACT] -> return signature -> Client
- Client -> broadcast EVM transaction (as the derived EVM account of the NEAR CONTRACT + a path e.g. "ethereum,1")
The only way to obtain a valid ecdsa signature for the derived EVM account is by calling the NEAR contract's sign method.
If the derived EVM account is, for example, the owner of an NFT contract (like in the example above), then the only way to mint the NFT is by calling the NEAR contract first to obtain a valid signature for the owner.
The NEAR contract acts as a gatekeeper for the derived EVM account (or accounts). Expanding on this, complex applications can be built with their logic on NEAR and the execution happening on EVM or other chains!
sign
method accepts a payload that is the unhashed RLP encoded EVM transaction data e.g.6a627842000000000000000000000000525521d79134822a342d330bd91DA67976569aF1
calls the methodmint
with an address argument of525521d79134822a342d330bd91DA67976569aF1
PUBLIC_RLP_ENCODED_METHOD_NAMES
is a constant that stores EVM method name hashes that can be called by any NEAR account; e.g. the method namemint
hashes6a627842000000000000000000000000
- The
COST
of a public call is 1 NEAR token path
andkey_version
arguments are passed through to MPCsign
call, but in the future could be used as additional features by this contract for new applications or security
Install cargo-near
and near-cli
- cargo-near - NEAR smart contract development toolkit for Rust
- near CLI-rs - Iteract with NEAR blockchain from command line
cargo build
cargo near create-dev-account
cargo near deploy [ACCOUNT_ID]
NEAR_PROXY_ACCOUNT="true"
NEAR_PROXY_CONTRACT="true"
NEAR_PROXY_ACCOUNT_ID="futuristic-anger.testnet"
NEAR_PROXY_PRIVATE_KEY="ed25519:..."
With NEAR_PROXY_CONTRACT="true"
the script will call sign
method of the proxy contract you deployed using cargo near deploy
.
With NEAR_PROXY_ACCOUNT="false"
you will be calling the NEAR contract from your own NEAR account specified in the .env
. You will only be able to call sign for an EVM transaction that contains the rlp encoded method name mint
. Why? Because otherwise, any NEAR account could get a valid signature for any EVM transaction for the derived EVM account of the proxy contract. This is an example of the Access Control and Protocol Application Logic section above.
let owner = env::predecessor_account_id() == env::current_account_id();
// check if rlp encoded eth transaction is calling a public method name
let mut public = false;
for n in PUBLIC_RLP_ENCODED_METHOD_NAMES {
if rlp_payload.find(n).is_some() {
public = true
}
}
// only the NEAR contract owner can call sign of arbitrary payloads for chain signature accounts based on env::current_account_id()
if !public {
require!(
owner,
"only contract owner can sign arbitrary EVM transactions"
);
}
- Your contract should be deployed and you should have the following env vars:
NEAR_PROXY_ACCOUNT="true"
NEAR_PROXY_CONTRACT="true"
NEAR_PROXY_ACCOUNT_ID="..."
NEAR_PROXY_PRIVATE_KEY="ed25519:..."
- Call
yarn start -etx
you are now deriving an Ethereum address using the NEAR account ID of the NEAR Proxy Contract, not your NEAR account ID
NOTE: This Ethereum address is different and unfunded. So, this transaction will not work.
- Fund the address from step 2.
You can do this by sending ETH using this script.
Change env vars:
NEAR_PROXY_ACCOUNT="false"
NEAR_PROXY_CONTRACT="false"
Send ETH to your new derived Ethereum address:
yarn start -etx --to 0x[ADDRESS FROM STEP 2] --amount [AMOUNT IN ETH e.g. 0.1]
Don't forget to change these back!
NEAR_PROXY_ACCOUNT="true"
NEAR_PROXY_CONTRACT="true"
- Now you can repeat the steps from the Ethereum EVM Contract NFT Example above.
You will deploy the NFT contract to an EVM account derived from the EVM account derived (not a typo) from the NEAR contract address.
- [NEAR CONTRACT] -> [Derived EVM account] -> [EVM contract]
Once you get the signature from calling the NEAR contract, to broadcast your transaction, you will call the mint
method as the owner of the deployed NFT contract.
- [Derived EVM account] -> [EVM contract]
- Once the NFT contract is deployed you can call the NEAR contract from any NEAR account. Try it by changing the
.env
vars:
NEAR_PROXY_ACCOUNT="false"
NEAR_PROXY_CONTRACT="true"
This will make the NEAR call come from your original NEAR account (top of your .env file) instead of calling as the NEAR contract owner.
TBD