TypeScript client for Salt, an open MPC self-custodial infrastructure for organisations. With Salt, anyone can spin up a system of self-sovereignty for self-custodial wealth management, including delegations to 3rd parties such as asset managers, robo-advisors or agents.
⚠️ Pre-release software. Before upgrading:
npm install salt-sdk viem
viem is a peer dependency.
import { Salt } from 'salt-sdk';
import {
createPublicClient,
createWalletClient,
http,
parseEther,
type Hex,
} from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrumSepolia, sepolia } from 'viem/chains';
// Create an instance — TESTNET uses Arbitrum Sepolia for orchestration
const salt = new Salt({ environment: 'TESTNET' });
// Set up a viem wallet client (orchestration chain must match the environment)
const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const walletClient = createWalletClient({
account,
chain: arbitrumSepolia,
transport: http(),
});
// Authenticate with SIWE
await salt.authenticate(walletClient);
// Fetch your organisations and accounts
const orgs = await salt.getOrganisations();
const accounts = await salt.getAccounts(orgs[0].id);
// Submit a native ETH transfer to Sepolia
const publicClient = createPublicClient({
chain: sepolia,
transport: http(process.env.SEPOLIA_RPC_URL),
});
const ceremony = await salt.submitTx({
accountId: accounts[0].id,
to: '0x000000000000000000000000000000000000dEaD',
value: parseEther('0.01'),
chainId: 11155111,
userAddress: account.address,
walletClient,
publicClient,
});
// Track progress: proposing → signing → broadcasting → confirming → success | failure
ceremony.on('stateChanged', ({ stage }) => console.log('tx is now', stage));
// Wait for MPC signing + broadcast to complete
const { transaction } = await ceremony.wait();
console.log('tx hash:', transaction.txHash);
See More: Salt constructor · authenticate · getOrganisations · getAccounts · submitTx
Every Salt transaction involves two networks:
environment:
STAGING & TESTNET = Arbitrum SepoliaMAINNET = Arbitrum OnechainId + publicClient in submitTx. Can be any supported EVM chain.const ceremony = await salt.submitTx({
/* ... */
});
ceremony.on('stateChanged', ({ stage }) => {
console.log('transaction is now', stage);
// proposing → signing → broadcasting → confirming → success | failure
});
const { transaction } = await ceremony.wait();
See More: submitTx · TransactionHostCeremony
Policies control which transactions robo guardians will co-sign. Create them per account and chain. If a transaction violates a policy, the robos refuse to sign and it fails before broadcast.
// Restrict ERC-20 approve() calls: only allow a specific spender, cap the amount
await salt.createAccountPolicy({
accountId: accounts[0].id,
organisationId: orgs[0].id,
type: 'contract_param_restriction',
chain: '11155111', // Sepolia
params: {
restrictions: [
{
contractAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // USDC on Sepolia
functionSignature: 'approve(address,uint256)',
paramIndex: 0, // spender argument
operator: 'eq',
value: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
},
{
contractAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238',
functionSignature: 'approve(address,uint256)',
paramIndex: 1, // amount argument
operator: 'lte',
value: '1000000', // 1 USDC (6 decimals)
},
],
},
});
See More: createAccountPolicy · ContractParamRestriction · PolicyParams
Pass encoded calldata via data. The transaction below is valid against the contract_param_restriction policy above — the spender matches and the amount is within the cap.
import { encodeFunctionData, parseAbi } from 'viem';
const data = encodeFunctionData({
abi: parseAbi(['function approve(address spender, uint256 amount)']),
functionName: 'approve',
args: [
'0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', // allowed spender
500_000n, // 0.5 USDC — within the 1 USDC cap
],
});
const ceremony = await salt.submitTx({
accountId: accounts[0].id,
to: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // USDC on Sepolia
value: 0n,
chainId: 11155111,
data,
userAddress: account.address,
walletClient,
publicClient,
});
await ceremony.wait();
See More: submitTx · SendTransactionParams
Organisations group collaborators and own accounts. Accounts are MPC wallets: humans create them via a key-generation ceremony and become signers. Fetch what the authenticated user belongs to with getOrganisations and getAccounts, create new ones with createOrganisation and createAccount, and manage collaborators with inviteCollaborator.
See examples: createOrganisation · inviteCollaborator · updateCollaborator · createAccount · getOrganisations · getAccounts
Robos belong to an organisation, and are automated co-signers for accounts. Manage robo hosts with createRoboHost and getRoboHost.
See examples: Salt constructor · createRoboHost · getRoboHost · RoboHost
Full API reference and more examples at kagamidigital.github.io/salt-sdk-mirror.