This TypeScript SDK provides tools for working with Ethereum stealth addresses as defined in EIP-5564 and EIP-6538. It aims to offer a comprehensive suite of functionalities for both generating stealth addresses and interacting with stealth transactions.
For comprehensive documentation and to learn more about stealth addresses, please visit our official documentation site.
Information about contract deployments can be found on the deployments page of our official documentation site.
- Generate Ethereum stealth addresses.
- Compute stealth address private keys.
- Check stealth address announcements to determine if they are intended for a specific user.
- Look up the stealth meta address for a registrant
- Fetch announcements
- Watch announcements for a user
- Prepare the payload for announcing stealth address details
- Prepare the payload for registering a stealth meta-address on someone's behalf
npm install @scopelift/stealth-address-sdk
# or
yarn add @scopelift/stealth-address-sdk
# or
bun install @scopelift/stealth-address-sdkTests default to using your local anvil node
anvil
bun run testAlternatively, run your tests using a fork of your provided (RPC_URL in env) rpc url
bun run anvil-fork
# run all tests
bun run test-fork
# or for a specific file
bun run test-fork FILE={file path}import { generateStealthAddress } from "@scopelift/stealth-address-sdk";
// Your stealth meta-address URI
// Follows the format: "st:<chain>:<stealthMetaAddress>", where <chain> is the chain identifier (https://eips.ethereum.org/EIPS/eip-3770#examples) and <stealthMetaAddress> is the stealth meta-address.
const stealthMetaAddressURI = "...";
// Generate a stealth address using the default scheme (1)
// To learn more about the initial implementation scheme using SECP256k1, please see the reference here (https://eips.ethereum.org/EIPS/eip-5564)
const result = generateStealthAddress({ stealthMetaAddressURI });
// Use the stealth address
console.log(result.stealthAddress);import {
computeStealthKey,
VALID_SCHEME_ID,
} from "@scopelift/stealth-address-sdk";
// Example inputs
const viewingPrivateKey = "0x..."; // Viewing private key of the recipient
const spendingPrivateKey = "0x..."; // Spending private key of the recipient
const ephemeralPublicKey = "0x..."; // Ephemeral public key from the sender's announcement
const schemeId = VALID_SCHEME_ID.SCHEME_ID_1; // Scheme ID, currently only '1' is supported
// Compute the stealth private key
const stealthPrivateKey = computeStealthKey({
viewingPrivateKey,
spendingPrivateKey,
ephemeralPublicKey,
schemeId,
});import {
checkStealthAddress,
VALID_SCHEME_ID,
} from "@scopelift/stealth-address-sdk";
// Example inputs
const ephemeralPublicKey = "0x..."; // The ephemeral public key from the announcement
const spendingPublicKey = "0x..."; // The user's spending public key
const userStealthAddress = "0x..."; // The user's stealth address
const viewingPrivateKey = "0x..."; // The user's viewing private key
const viewTag = "0x..."; // The view tag from the announcement
const schemeId = VALID_SCHEME_ID.SCHEME_ID_1; // Scheme ID, currently only '1' is supported
// Check if the announcement is intended for the user
const isForUser = checkStealthAddress({
ephemeralPublicKey,
schemeId,
spendingPublicKey,
userStealthAddress,
viewingPrivateKey,
viewTag,
});
console.log(
isForUser
? "Announcement is for the user"
: "Announcement is not for the user"
);import {
ERC5564_CONTRACT_ADDRESS,
VALID_SCHEME_ID,
createStealthClient,
} from "@scopelift/stealth-address-sdk";
// Example parameters
const chainId = 11155111; // Example chain ID for Sepolia
const rpcUrl = process.env.RPC_URL; // Your env rpc url that aligns with the chainId;
const fromBlock = BigInt(12345678); // Example ERC5564 announcer contract deploy block for Sepolia, or the block in which the user registered their stealth meta address (as an example)
// Initialize the stealth client
const stealthClient = createStealthClient({ chainId, rpcUrl: rpcUrl! });
// Use the address of your calling contract if applicable
const caller = "0xYourCallingContractAddress";
// Your scheme id
const schemeId = BigInt(VALID_SCHEME_ID.SCHEME_ID_1);
// The contract address of the ERC5564Announcer on your target blockchain
// You can use the provided ERC5564_CONTRACT_ADDRESS get the singleton contract address for a valid chain ID
const ERC5564Address = ERC5564_CONTRACT_ADDRESS;
async function fetchAnnouncementsForUser() {
// Example call to getAnnouncements action on the stealth client to get all potential announcements
// Use your preferred method to get announcements if different, and
// adjust parameters according to your requirements
const announcements = await stealthClient.getAnnouncements({
ERC5564Address,
args: {
schemeId,
caller,
// Additional args for filtering, if necessary
},
fromBlock, // Optional fromBlock parameter (defaults to 0, which can be slow for many blocks)
});
// Example call to getAnnouncementsForUser action on the stealth client
// Adjust parameters according to your requirements
const userAnnouncements = await stealthClient.getAnnouncementsForUser({
announcements,
spendingPublicKey: "0xUserSpendingPublicKey",
viewingPrivateKey: "0xUserViewingPrivateKey",
});
return userAnnouncements;
}Use getAnnouncementsPageUsingSubgraph when you want deterministic cursor-based
pagination without building raw filter strings yourself.
import { getAnnouncementsPageUsingSubgraph } from "@scopelift/stealth-address-sdk";
const firstPage = await getAnnouncementsPageUsingSubgraph({
subgraphUrl: "https://your-subgraph.example/api",
});
console.log(firstPage.announcements);
console.log(firstPage.nextCursor); // present only when another page exists
console.log(firstPage.snapshotBlock); // required for every later page in the same scanScan filters are optional on the initial page.
pageSizedefaults to999pageSizemust be between1and999- omitting
fromBlock,toBlock,schemeId, andcallermeans no filter - the initial page must omit both
cursorandsnapshotBlock - the SDK resolves
snapshotBlockon the initial page and returns it - every subsequent page must provide both
cursorandsnapshotBlock
import { getAnnouncementsPageUsingSubgraph } from "@scopelift/stealth-address-sdk";
const firstPage = await getAnnouncementsPageUsingSubgraph({
subgraphUrl: "https://your-subgraph.example/api",
fromBlock: 12345678,
toBlock: 12349999,
schemeId: 1n,
caller: "0x1234567890123456789012345678901234567890",
pageSize: 100,
});
for (const announcement of firstPage.announcements) {
console.log(announcement.transactionHash);
}
let cursor = firstPage.nextCursor;
while (cursor) {
const page = await getAnnouncementsPageUsingSubgraph({
subgraphUrl: "https://your-subgraph.example/api",
fromBlock: 12345678,
toBlock: 12349999,
schemeId: 1n,
caller: "0x1234567890123456789012345678901234567890",
pageSize: 100,
cursor,
snapshotBlock: firstPage.snapshotBlock,
});
for (const announcement of page.announcements) {
console.log(announcement.transactionHash);
}
cursor = page.nextCursor;
}Pass the previous page's nextCursor and the initial page's snapshotBlock
back into the same bounded query to fetch the next older page deterministically.
If nextCursor is undefined, you are on the terminal page and no extra probe
request is needed.
cursor is pagination position. snapshotBlock is consistency. Reuse the same
snapshotBlock for every page in a multi-page scan so each request reads the
same frozen subgraph view.
Pagination is ordered by subgraph announcement id in descending order. The
cursor is the last returned id, reused as an exclusive id_lt boundary for
the next page, and page queries are pinned to one subgraph block snapshot.
getAnnouncementsUsingSubgraph remains available as the legacy eager helper.
It preserves the historical pageSize behavior for compatibility, while
getAnnouncementsPageUsingSubgraph is the typed cursor-based API.
MIT License