Not yet released. The packages on this page are not published to npm yet, so the install commands below will not resolve. The documented interface is stable and the code exists; only publication is outstanding. To use KeyStone from a frontend today, call the REST API or the TypeScript SDK, which is published. Contact us if you need early access.
Installation
Install the packages you need:npm install @keystoneos/react
npm install @keystoneos/react-ui
npm install @keystoneos/node
1. Create a Session Token (Backend)
Session tokens are scoped, short-lived JWTs that your frontend uses to call the KeyStone API. Your backend creates them using M2M credentials.// your-backend/routes/keystone-session.ts
import { KeystoneClient } from '@keystoneos/sdk';
import { createSessionToken } from '@keystoneos/node';
const keystone = new KeystoneClient({
clientId: process.env.KEYSTONE_CLIENT_ID,
clientSecret: process.env.KEYSTONE_CLIENT_SECRET,
environment: 'production',
});
app.post('/api/keystone/session', async (req, res) => {
const session = await createSessionToken(keystone, {
scopes: ['settlements:read', 'settlements:write'],
metadata: { userId: req.user.id },
});
res.json({ token: session.sessionToken });
});
2. Set Up the Provider (Frontend)
Wrap your app (or the relevant section) withKeystoneProvider:
import { KeystoneProvider } from '@keystoneos/react';
function App() {
const [token, setToken] = useState<string | null>(null);
useEffect(() => {
fetch('/api/keystone/session', { method: 'POST' })
.then(r => r.json())
.then(d => setToken(d.token));
}, []);
if (!token) return <Loading />;
return (
<KeystoneProvider
sessionToken={token}
environment="production"
onTokenExpired={async () => {
const res = await fetch('/api/keystone/session', { method: 'POST' });
const data = await res.json();
return data.token;
}}
>
<YourApp />
</KeystoneProvider>
);
}
3. Use Hooks
Now you can use any hook inside the provider:import { useSettlements, getStateInfo } from '@keystoneos/react';
function SettlementDashboard() {
const { settlements, total, isLoading } = useSettlements();
if (isLoading) return <Spinner />;
return (
<table>
<thead>
<tr><th>Reference</th><th>State</th><th>Created</th></tr>
</thead>
<tbody>
{settlements.map(s => {
const state = getStateInfo(s.state);
return (
<tr key={s.id}>
<td>{s.external_reference}</td>
<td>{state.label}</td>
<td>{new Date(s.created_at).toLocaleDateString()}</td>
</tr>
);
})}
</tbody>
</table>
);
}
4. Submit an Instruction
Create a settlement by submitting your side of the trade. A DvP instruction requires two legs - one for asset delivery and one for payment. The direction of each leg is derived from your role:import { useSubmitInstruction } from '@keystoneos/react';
function TradeForm() {
const { submit, result, isSubmitting, error } = useSubmitInstruction();
const handleSubmit = async (formData) => {
const instruction = await submit({
role: 'seller',
party: {
external_reference: formData.accountId,
name: formData.accountName,
wallet_address: formData.walletAddress,
},
legs: [
{
leg_type: 'asset_delivery',
instrument_id: formData.assetTokenAddress,
quantity: formData.assetQuantity,
direction: 'deliver', // seller delivers asset
chain_id: formData.chainId,
},
{
leg_type: 'payment',
instrument_id: formData.paymentTokenAddress,
quantity: formData.paymentQuantity,
direction: 'receive', // seller receives payment
chain_id: formData.chainId,
},
],
templateSlug: 'cross_platform_dvp',
timeoutAt: new Date(Date.now() + 86400000).toISOString(),
});
if (instruction.status === 'matched') {
// Counterparty already submitted - settlement created
router.push(`/settlements/${instruction.settlementId}`);
}
};
if (result && result.status === 'pending_match') {
return (
<div>
<p>Waiting for counterparty. Share this trade reference:</p>
<code>{result.tradeReference}</code>
</div>
);
}
return <YourFormComponent onSubmit={handleSubmit} loading={isSubmitting} error={error} />;
}
5. Handle Deposits (Tier 2)
For custody wallet integration, provide action delegates:<KeystoneProvider
sessionToken={token}
actionDelegates={{
onDepositRequired: async (leg, depositInfo) => {
// depositInfo contains: calldata, escrowAddress, chainId, tokenAddress, amount
const result = await fetch('/api/deposit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
calldata: depositInfo.calldata,
escrowAddress: depositInfo.escrowAddress,
chainId: depositInfo.chainId,
}),
}).then(r => r.json());
return { txHash: result.txHash };
},
}}
>
// your-backend/routes/deposit.ts
app.post('/api/deposit', async (req, res) => {
const { calldata, escrowAddress, chainId } = req.body;
// Submit to Fireblocks, BitGo, or your signing infrastructure
const txHash = await fireblocksClient.submitTransaction({
to: escrowAddress,
data: calldata,
chainId,
});
res.json({ txHash });
});
6. Handle Webhooks (Backend)
Set up webhook processing with signature verification:import { webhookMiddleware } from '@keystoneos/node';
import express from 'express';
app.post(
'/webhooks/keystone',
express.raw({ type: 'application/json' }),
webhookMiddleware({ secret: process.env.WEBHOOK_SECRET }),
(req, res) => {
const { event, data } = req.keystoneEvent;
switch (event) {
case 'settlement.state.awaiting_deposits':
console.log(`Settlement ${data.settlement_id}: ${data.state}`);
// Trigger deposits, update your OMS, notify traders, etc.
break;
case 'settlement.state.finalized':
console.log(`Settlement finalized: ${data.settlement_id}`);
break;
}
res.sendStatus(200);
},
);
Next: Hooks Reference
Explore the full hooks API.