Creates an instance of the Salt SDK
Optionalparams: { authToken?: string; environment: Environment }The constructor parameters
OptionalauthToken?: stringThe authentication token for the Salt SDK. See Salt.authenticate for the full authentication flow, or Salt#setAuthToken to set a pre-existing token after calling the constructor.
Environment to use. This will be optional in the future, but right now it is required. Use 'TESTNET'
InvalidParams thrown if the parameters passed in to the constructor are invalid. Params are optional - the default environment is TESTNET.
InvalidEnvironment Thrown if the provided Environment is invalid.
InvalidAuthToken Thrown if the provided auth token is invalid. The token must be a string. If you need to authenticate, simply omit ConstructorParams.authToken, then use authenticate.
import { Salt } from 'salt-sdk';
import { Wallet } from 'ethers';
const salt = new Salt({ environment: 'TESTNET' });
// Log in to an existing account
const signer = Wallet.createRandom();
await salt.authenticate(signer);
// You are ready to use the SDK. For example...
const orgs = await salt.getOrganisations();
import { Salt } from 'salt-sdk';
const salt = new Salt({ environment: 'MAINNET' });
Disconnect the websocket connection. Call this when you are done using a long-lived socket (e.g. after stopping a NudgeListener) to allow the Node.js process to exit cleanly.
Self-contained methods like createAccount and submitTx disconnect automatically — you need this for NudgeListener and resumeAccountSetup workflows where the socket must stay open until AccountSetup.waitForCompletion finishes.
Creates a new account within an Organisation. Deploys a vault smart contract, registers the account, and nudges co-signers — but does not block on the MPC huddle.
Returns an AccountSetup whose accountId
is available immediately. Call waitForCompletion()
to enter the huddle, complete key generation, and finalize the vault.
If you skip waitForCompletion(), the account is persisted and can be
resumed later with resumeAccountSetup.
The SDK automatically determines the required robo signers based on the number of human signers provided.
The account creation parameters
An AccountSetup with the account ID and a waitForCompletion() method
InvalidAuthToken if the authentication token is invalid
InvalidSigner if the signer is missing or invalid
WrongChain if the signer is on a different chain than expected
InsufficientGas if the signer lacks funds for gas on the orchestration chain
SocketConnectError if the signer fails to connect to websockets, which are required for setup orchestration
import { Salt } from 'salt-sdk';
import { Wallet, providers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const provider = new providers.JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.PRIVATE_KEY).connect(provider);
const setup = await salt.createAccount({
name: 'Treasury',
organisationId: 'org-id',
signers: [
'0x1111111111111111111111111111111111111111',
'0x2222222222222222222222222222222222222222',
],
signer,
});
console.log('Account ID:', setup.accountId);
const { account } = await setup.waitForCompletion();
console.log('Account created:', account.publicKey);
const setup = await salt.createAccount({
name: 'Treasury',
organisationId: 'org-id',
signers: ['0xCreator', '0xCoSigner'],
signer,
});
// Save the account ID — don't call waitForCompletion()
const accountId = setup.accountId;
// Later, all signers resume:
const { account } = await salt.resumeAccountSetup({ accountId, signer });
Gets the details of a specific account. The user must have adequate permissions to view the account.
The ID of the account
The account details
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
Gets the list of token balances for an account. This list is not exhaustive. When options.networks is omitted, defaults depend on the SDK environment (testnet: Sepolia + Arbitrum Sepolia; mainnet: Ethereum + Arbitrum One). Unsupported or unknown network IDs are silently ignored by the API — they will not cause an error, but no tokens will be returned for those networks.
The ID of the account
Optionaloptions: GetAccountTokensOptionsOptional override for which networks to query
The list of tokens & balances
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const tokens = await salt.getAccountTokens('account-id');
tokens.forEach(t => console.log(`${t.symbol}: ${t.balance}`));
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const tokens = await salt.getAccountTokens('account-id', { networks: ['1', '8453'] });
tokens.forEach(t => console.log(`${t.symbol}: ${t.balance}`));
Gets the transactions of a specific account. The user must have adequate permissions to view the account's transactions.
The ID of the account
A list of transactions for the account
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
Opens an API connect to receive account nudges. Nudges are ephemeral requests from another Organisation Member asking this user to join an account creation process.
The nudge listener can be used to accept or reject nudges, and retrieve the details for the account creation process so that this API client can participate in the key material generation process.
Ethers signer, used during the account creation process
NudgeListener for the full NudgeListener description
InvalidAuthToken if the authentication token is invalid
InvalidSigner if the signer is missing or invalid
WrongChain if the signer is on a different chain than expected
SocketConnectError if the signer fails to connect to websockets, which are required for setup orchestration
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
const walletWithProvider = ethers.Wallet.createRandom().connect(provider);
const salt = new Salt({
environment: "TESTNET",
});
// from the nudge listener you can observe and manage your signer's reaction to nudges
const nudgeListener = await salt.listenToAccountNudges(walletWithProvider);
// read all the nudges in the queue
const nudgeQueue = nudgeListener.getNudgeQueue();
// read all the accounts for which the nudge has been processed
const processedAccounts = nudgeListener.getAccounts();
// stop listening to nudges
nudgeListener.disableNudgeListener();
// start listening again
nudgeListener.enableNudgeListener();
// when done, disconnect the websocket to allow the process to exit
salt.disconnect();
Sends a nudge to one or more co-signers, prompting them to join an account creation ceremony. Use this to re-nudge signers if the ceremony times out or a co-signer was not listening when the account was first created.
Combine with resumeAccountSetup to re-enter the ceremony yourself after nudging offline co-signers.
When to is provided, only that address is nudged. When omitted, all
signers on the account except the caller are nudged.
Nudge parameters: accountId (the account whose signers to nudge), signer (ethers signer of the caller), and optional to (address of a specific signer to nudge — if omitted, all other signers are nudged)
InvalidAuthToken if the authentication token is invalid
InvalidSigner if the signer is missing or invalid
WrongChain if the signer is on a different chain than expected
SocketConnectError if the signer fails to connect to websockets, which are required for nudge delivery
import { Salt } from 'salt-sdk';
import { Wallet, providers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const provider = new providers.JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.PRIVATE_KEY).connect(provider);
await salt.nudge({ accountId: 'account-id', signer });
import { Salt } from 'salt-sdk';
import { Wallet, providers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const provider = new providers.JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.PRIVATE_KEY).connect(provider);
await salt.nudge({
accountId: 'account-id',
signer,
to: '0x2222222222222222222222222222222222222222',
});
Resumes an account setup that did not complete. Returns an AccountSetup configured for the resume path — call AccountSetup.waitForCompletion to re-enter the MPC huddle.
Both creators and non-creators can call this method — the caller's role is detected automatically from the account's creator address. Non-creators only need this if they are not already listening for nudges via listenToAccountNudges, which handles rejoining automatically.
The resume parameters: accountId, signer, and optional timeoutMs
An AccountSetup — call .waitForCompletion() to run the huddle
When to resume vs. start fresh: Use this method when
createAccount failed after the vault was deployed on-chain
(e.g. the huddle timed out, a signer went offline, or vault
finalization failed). If createAccount failed before vault
deployment (validation errors, robos offline, insufficient gas for
deploy), simply call createAccount again instead.
Timeout: The huddle has a built-in timeout (default 5 minutes,
max 10 minutes). Override via params.timeoutMs. When the timeout
fires, waitForCompletion() rejects with HuddleTimeout.
Lifecycle events: Listen on the returned AccountSetup for
diagnostic events (connected, preRegistered, ready, registering,
registered, signerStatusChanged, timedOut).
InvalidAuthToken if the authentication token is invalid
InvalidSigner if the signer is missing or invalid
WrongChain if the signer is on a different chain than expected
SocketConnectError if the signer fails to connect to websockets
ApiError if the account cannot be fetched
import { Salt } from 'salt-sdk';
import { Wallet, providers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const provider = new providers.JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.PRIVATE_KEY).connect(provider);
const setup = await salt.resumeAccountSetup({
accountId: 'acc-123',
signer,
timeoutMs: 120_000, // 2 minutes
});
// Listen for diagnostic events
setup.on('connected', (id) => console.log('Connected to huddle:', id));
setup.on('signerStatusChanged', (s) => console.log('Signer statuses:', s));
setup.on('timedOut', (id) => console.log('Huddle timed out:', id));
const { account } = await setup.waitForCompletion();
console.log('Account resumed:', account.publicKey);
import { Salt } from 'salt-sdk';
import { Wallet, providers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const provider = new providers.JsonRpcProvider(process.env.RPC_URL);
const signer = new Wallet(process.env.PRIVATE_KEY).connect(provider);
await salt.nudge({ accountId: 'acc-123', signer });
const setup = await salt.resumeAccountSetup({
accountId: 'acc-123',
signer,
});
const { account } = await setup.waitForCompletion();
Initiate the authentication flow to get an authentication token from the Salt API. This will require a signature from the Signer, which will be used to authenticate the user. If you already have an authentication token, you can set it using the setAuthToken method, or when you call the "constructor".
A signer is required to complete the SIWER authentication flow. It is used to sign a message and a nonce, which are then validated by the Salt acceptInvitation and an auth token is returned. If you are using a browser based signer, such as Metamask, you will need to manually approve to the signature request.
The ethers signer to use for the authentication flow
long lived authentication token. This is also set on the client instance, so you do not normally need to store this manually
InvalidUrl if the API URL or path is invalid
import { Salt } from 'salt-sdk';
import { ethers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
});
const signer = ethers.Wallet.createRandom();
await salt.authenticate(signer);
import { Salt } from 'salt-sdk';
import { ethers } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
});
const signer = ethers.Wallet.createRandom();
const authToken = await salt.authenticate(signer);
// Note: this is not recommended as a secure way to store the auth token
localStorage.setItem('salt-auth-token', authToken);
Sets the authentication token for the Salt SDK. This is useful if you have previously logged in and stored the token. You can also provide the token to the constructor If you do not have a token already, you can use the authenticate method to obtain one.
The authentication token to use for this connection
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
});
// Retrieve a previously stored auth token (note: this is not recommended as a secure way to store a token)
const authToken = localStorage.getItem('authToken');
salt.setAuthToken(authToken);
// You are ready to use the SDK. For example...
await salt.getBalance();
Accepts an Invitation to an Organisation. The list of transactions for the current user can be retrieved with getOrganisationsInvitations.
The ID of the invitation to accept
InvalidAuthToken if the authentication token is invalid
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const invitations = await salt.getOrganisationsInvitations();
for (const invitation of invitations) {
console.log(`Accepting invitation to organisation ${invitation.organisation_id}`);
await salt.acceptOrganisationInvitation(invitation.id);
}
Creates a new Organisation. The authenticated user will be the owner. Collaborators can optionally be invited at creation time — they will receive invitations that can be accepted via acceptOrganisationInvitation.
This operation is off chain, so it does not require any gas.
The organisation creation parameters
The created organisation
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
ApiError if the API returns an error (e.g. duplicate member addresses, invalid name)
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const org = await salt.createOrganisation({
name: 'My Organisation',
owner: {
name: 'Alice',
address: '0x1234567890123456789012345678901234567890',
role: 'CEO',
},
});
console.log('Created organisation:', org._id);
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const org = await salt.createOrganisation({
name: 'My Organisation',
owner: {
name: 'Alice',
address: '0x1234567890123456789012345678901234567890',
role: 'CEO',
},
collaborators: [
{
name: 'Bob',
address: '0x2345678901234567890123456789012345678901',
role: 'CFO',
accessLevel: 2,
},
],
});
Gets the list of Accounts that belong to a Organisation.
The ID of the Organisation
The list of accounts
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const accounts = await salt.getAccounts('organisation-id');
accounts.forEach(account => {
console.log(`Account: ${account.name} ID: ${account.id} Public key: ${account.publicKey}`);
});
Gets the list of Organisations that the current user is a member of. Requires authentication.
The list of organisations
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const organisations = await salt.getOrganisations();
organisations.forEach(org => {
console.group('Organisation:', org.name);
org.members.forEach(member => {
console.log('Member:', member.name);
});
console.groupEnd();
});
Returns pending Invitation to an Organisation for the current user. They can be accepted using acceptOrganisationInvitation.
List of invitations that are pending for this user
InvalidAuthToken if the authentication token is invalid
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const invitations = await salt.getOrganisationsInvitations();
if (invitations.length > 0) {
console.log('You have pending invitations');
} else {
console.log('No pending invitations');
}
Creates a new Policy on a Salt account. Policies control what transactions are allowed, denied, or require approval before being signed by the robo guardians.
The policy creation parameters
The created policy
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
ApiError if the API returns an error (e.g. invalid policy type or params)
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const policy = await salt.createAccountPolicy({
accountId: 'account-id',
type: 'allowed_recipients',
chain: '11155111', // Sepolia
params: {
recipients: [
{ address: '0x1234567890123456789012345678901234567890', nickname: 'Treasury' },
],
},
});
console.log('Created policy:', policy.id);
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const policy = await salt.createAccountPolicy({
accountId: 'account-id',
type: 'transaction_limit_token_denominated',
chain: '1', // Ethereum Mainnet
params: {
limits: [
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
amount: '1000',
},
],
},
});
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const policy = await salt.createAccountPolicy({
accountId: 'account-id',
type: 'nominated_approvers',
chain: '1',
params: {
approvers: [
{ address: '0x1234567890123456789012345678901234567890' },
],
},
});
Fetches an existing Policy by its ID.
The ID of the policy to retrieve
The policy
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
ApiError if the policy cannot be found or the API returns an error
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const policy = await salt.getAccountPolicy('policy-id');
console.log(`Policy type: ${policy.type}`);
console.log(`Policy chain: ${policy.chain}`);
console.log('Policy params:', policy.params);
Updates an existing Policy by its ID. Replaces the policy's params with the new values provided.
The ID of the policy to update
The new policy parameters
The updated policy
InvalidAuthToken if the authentication token is invalid. See authenticate for how to authenticate.
ApiError if the API returns an error (e.g. policy not found or invalid params)
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const updated = await salt.updateAccountPolicy('policy-id', {
recipients: [
{ address: '0x1234567890123456789012345678901234567890', nickname: 'Treasury' },
{ address: '0x2345678901234567890123456789012345678901', nickname: 'Payroll' },
],
});
console.log('Updated policy:', updated.id);
Gets the online status of a robo's guardians for a given account. This indicates whether the robo signers are currently connected and available for signing operations.
The ID of the account to check robo status for
The robo status with id and online boolean
InvalidAuthToken if the authentication token is invalid
ApiError if the API returns an error (i.e. robos do not exist, insufficient permissions to view status)
import { Salt } from 'salt-sdk';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const status = await salt.getRoboStatus('account-id');
if (status.online) {
console.log('Robo guardians are online, ready for signing');
} else {
console.log('Robo guardians are offline');
}
Gets the nonce for an account on a specific chain. The nonce is an incrementing number stored by Salt that is used to prevent replay attacks. submitTx use the nonce, but if you do not specify one the SDK will automatically use this function to populate it.
The ID of the account
The ID of the chain
The nonce
Gets the current gas price for a specific chain.
The ID of the chain
The current gas price
InvalidChain if the specified chain is unknown or unsupported
Initiate, sign and broadcast a generic transaction from a given Salt account. This method provides orchestrated transaction handling with automatic account detail fetching, multi-party signing coordination, and policy enforcement.
Both the vault (Salt account) and the signer must have sufficient funds to pay gas.
For sends, if gas and gasPrice are not provided, they will be estimated automatically. For contract deployment, gas is required (API does not yet support estimate for deploys).
Important: value is denominated in ETH (not wei). For example, pass '1' for 1 ETH,
'0.5' for 0.5 ETH, or '0.000000000000000001' for 1 wei.
This will require signing multiple transactions and requests:
The transaction parameters. Provide to for a regular send (SendSubmitTxParams) or omit it for a contract deployment (DeploySubmitTxParams), which requires data (compiled bytecode) and gas. Vault details will be fetched automatically. See SubmitTxParams for the full parameter definition
A Transaction object for orchestrated execution
InvalidAuthToken if the auth token is missing or invalid
InvalidSigner if the signer is missing or invalid
WrongChain if the signer is on a different chain than expected
ApiError if the API returns an error (i.e. account does not exist, insufficient permissions to view account)
ValidationError if the transaction parameters are invalid
TransactionError if the underlying SDK returns an unexpected result (e.g. a contract deployment that fails to produce a valid transaction response)
InsufficientGas if the transaction fails due to insufficient gas
SocketConnectError if the signer fails to connect to websockets, which are required for signing orchestration
import { Salt } from 'salt-sdk';
import { Wallet, utils } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const signer = Wallet.createRandom();
// Encode the approve function call
const iface = new utils.Interface(['function approve(address spender, uint256 amount)']);
const data = iface.encodeFunctionData('approve', [
'0xE592427A0AEce92De3Edee1F18E0157C05861564', // Uniswap V3 SwapRouter
'1000000000' // 1000 USDC (6 decimals)
]);
// Gas is estimated automatically when not provided
await salt.submitTx({
accountId: '0a1b2c3d4e5f',
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC contract
value: '0',
chainId: 1,
data: data,
signer: signer,
});
import { Salt } from 'salt-sdk';
import { Wallet, utils } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const signer = Wallet.createRandom();
const iface = new utils.Interface(['function approve(address spender, uint256 amount)']);
const data = iface.encodeFunctionData('approve', [
'0xE592427A0AEce92De3Edee1F18E0157C05861564',
'1000000000'
]);
await salt.submitTx({
accountId: '0a1b2c3d4e5f',
to: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
value: '0',
chainId: 1,
data: data,
signer: signer,
gas: '100000',
gasPrice: '20000000000', // 20 gwei
});
import { Salt } from 'salt-sdk';
import { Wallet, utils } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token'
});
const signer = Wallet.createRandom();
// Encode the submit function call for Lido staking
const iface = new utils.Interface(['function submit(address _referral) payable returns (uint256)']);
const data = iface.encodeFunctionData('submit', [
'0x0000000000000000000000000000000000000000' // No referral
]);
const tx = await salt.submitTx({
accountId: '0a1b2c3d4e5f',
to: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', // Lido stETH contract
value: '1', // 1 ETH
chainId: 1,
data: data,
signer: signer
});
const result = await tx.wait();
import { Salt } from 'salt-sdk';
import { Wallet } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const signer = Wallet.createRandom();
// Omit `to` to submit a contract creation transaction.
// `data` should contain the compiled contract bytecode; `gas` is required (API does not yet estimate for deploys).
const tx = await salt.submitTx({
accountId: '0a1b2c3d4e5f',
value: '0',
chainId: 11155111,
gas: '300000',
data: '0x608060...', // compiled bytecode
signer: signer,
});
const result = await tx.wait();
import { Salt, TransactionError, InsufficientGas } from 'salt-sdk';
import { Wallet } from 'ethers';
const salt = new Salt({
environment: 'TESTNET',
authToken: 'your-auth-token',
});
const signer = Wallet.createRandom();
try {
const tx = await salt.submitTx({
accountId: '0a1b2c3d4e5f',
value: '0',
chainId: 11155111,
gas: '300000',
data: '0xDEAD', // not valid contract bytecode
signer: signer,
});
await tx.wait();
} catch (error) {
if (error instanceof TransactionError) {
console.error('Transaction failed:', error.message);
console.error('Raw result:', error.data);
} else if (error instanceof InsufficientGas) {
console.error('Not enough gas:', error.message);
} else {
throw error;
}
}
The Salt SDK