Please see the following repos for Chain Signatures libraries and examples:
- https://github.com/near/multichain-tools (JS tools)
- https://github.com/EdsonAlcala/omni-transaction-rs (Rust rools)
- https://github.com/near-examples/chainsig-script (JS CLI and examples)
- https://github.com/mattlockyer/near-trade-signatures (JS multi-chain wallet signing and transaction broadcasting)
- Developers will be able to create transactions for different chains using the same high level API methods.
- The signing of transactions using NEAR's Chain Signatures protocol is abstracted and works with Node/Browser.
If you would like to contribute a new chain to this library please:
- Create a new folder titled the chain name for example "Ethereum" in the chains folder.
- Implement all methods as defined in this README.
- Follow the Ethereum example.
Currently, this can be implemented for any ECDSA-based chain.
yarn && yarn test
Ethereum is currently scaffolded based on https://github.com/near-examples/chainsig-script.
There are tests yarn test
that include up to getting an ethereum balance.
In order to proceed with an ethereum transaction (and test) you will have to add the following environment variables.
NEAR_ACCOUNT_ID="[NEAR_TESTNET_ACCOUNT]"
NEAR_PRIVATE_KEY="[NEAR_ACCOUNT_PRIVATE_KEY]"
MPC_PATH="[MPC_PATH]"
MPC_CHAIN="[ethereum|bitcoin]"
MPC_CONTRACT_ID="multichain-testnet-2.testnet"
MPC_PUBLIC_KEY="secp256k1:4HFcTSodRLVCGNVcGc4Mf2fwBBBxv9jxkGdiW2S2CA1y6UpVVRWKj6RX7d7TDt65k2Bj3w9FU4BGtt43ZvuhCnNt"
NEAR's Chain Signatures protocol allows any NEAR account to sign transactions for any chain. The signing step is a method call with signature:
sign(
payload: [u8; 32],
path: String,
key_version: u32,
)
This is executed by a NEAR smart contract that returns the signature to the client. The client then combined this signature with their transaction payload and broadcasts it to the network of their choice.
The creation of secp256k1 public keys for Bitcoin, EVM and other chains that use this signature scheme are currently supported.
For a full list of methods, see Methods section below. The following section describes a typical usage pattern.
import { near, bitcoin, ethereum } from '@near/chain-sig-lib';
// initialize a NEAR connection, if you don't already have one in your client, this can be useful for testing
const nearConnection = near.init(network: string, [accountId], [accountSecretKey]);
// initialize the bitcoin module for use with the current NEAR connection (this can be your existing NEAR connection e.g. from wallet-selector)
bitcoin.init(path, keyVersion, [nearConnection]);
// get the balance of bitcoin for the currently initialized derived address created by NEAR accountId, path, key_version
// optional argument: minusGas of a basic transfer
const balance = bitcoin.getBalance([minusGas]);
// optional check balance against use specified amount to transfer
...
// get a bitcoin transfer payload (amount is in sats)
const baseTransaction = bitcoin.transferBase(to: string, amount, [maxUTXOs]);
// sign the bitcoin payload - creates a NEAR transaction using either wallet or secretKey if passed to near.init
const [signedTransaction] = bitcoin.sign([baseTransaction]);
// broadcast the bitcoin transaction
const txHash = await bitcoin.broadcast([signedTransaction], network);
Shorthand calls will automatically check balance and throw an error before redirecting to a wallet.
Additionally, the above call to transferPayload
will also throw an error before creating the payload to be signed.
// optional shorthand for the last 3 steps
const txHash = await bitcoin.transfer(to: string, amount, network: string, [maxUTXOs]);
When using a web wallet, the txHash will not be returned. Instead you can fetch the result of the attempted NEAR transaction signing once redirected by the app using the following code:
import { near, ... } from '@near/chain-sig-lib';
// your code to create, sign and broadcast a transaction
...
// after web wallet redirect to your application e.g. typically in a useEffect hook in React
const { errors, [txHashes], [signedTransactions] } = near.getResults();
Smart contract chains (EVM and others) will typically have 1 state manipulating call, requiring gas to be spent and one view call. In order to encapsulte this and make it easier, there are some higher order methods, built on top of lower level primatives.
// viewing contract state
// TODO UPDATE
const payload = ethereum.viewBase(to: string, method, args);
// or the shorthand to sign with NEAR account and broadcast all together
const txHash = await ethereum.view(to: string, method, args, network);
// changing contract state
// get balance minus the gas required to execute the transaction
const balance = await ethereum.getBalance([minusGas, rawTx]);
// optional check if balance is negative, meaning callPayload (and call) will throw errors
...
const payload = ethereum.callBase(to: string, method, args);
// or the shorthand to sign with NEAR account and broadcast all together
const txHash = await ethereum.call(to: string, method, args, network);
For all chains, the following methods are available:
// initializes the chain instance
// if you need to use multiple paths, call again and it will overwrite the current settings with a new path for example
[chainInstance].init(path: string, keyVersion: uint, [nearConnection: object]);
// get the address for the current chain instance settings
// derived address == nearAccountId x path x keyVersion
// @returns string - hex/base58/... address depends on chain
getAddress();
// @async
// get a gas price estimate for the current chain
// @returns - object - values are chain dependent
getGas();
// @async
// get the balance of the derived address in the chain's native currency
// @returns {object} balance - values are chain dependent
getBalance();
// get a base transaction to transfer the chain's native currency
// @param [option, ...] - chain specific options e.g. maxUTXOs for Bitcoin
// @returns object baseTransaction
transferBase(to: string, amount: string, [option, ...]);
// @async
// sign the base transaction prompting the user to sign a NEAR transaction
// @param {object} baseTransactions - array of base transactions
// @returns {object[]} signedTransactions - signed transactions as array
sign(baseTransactions: object[]);
// @async
// broadcast the transaction to the network specified
// @param {object[]} signedTransactions
// @param {string} network - name of network to broadcast to
// @returns {string[]} txHash
broadcast(signedTransactions: object[], network: string);
// @async
// shorthand to skip the above three methods
// will prompt user to sign the NEAR transaction
// @returns {string[]} txHash
transfer(to: string, amount: string, network: string, [option: object, ...])
// @async
// if the user was redirect to a web wallet, get the results once returned to the application e.g. in useEffect hook when React component for chain signatures loads
// requires the top level near import from library
// @returns {object} results - { errors, [txHashes], [signedTransactions] }
near.getResults();
For smart contract chains, additional methods are available:
// because of the large number of bytes this is a special base transaction for deploying smart contracts
// @returns object baseTransaction
deployContractBase(bytes: string)
// call a method of a smart contract
// @returns object baseTransaction
callBase(to: string, methodName: string, args: object)
// view the result of a method call to a contract
// @returns object baseTransaction
viewBase(to: string, methodName: string, args: object)
// and the shorthands
// @async
deployContract(bytes: string, network: string)
// @async
call (to: string, methodName: string, args: object, network: string)
// @async
view (to: string, methodName: string, args: object, network: string)
- Cosmos - IBC
- Dfinity - Canisters
Live Example - NEAR Testnet, Sepolia, Bitcoin Testnet