Develop with Curvy SDK
Getting Started
IMPORTANT
Curvy SDK is currently in closed beta.
To get your own API key for testing, please first submit this Form
To receive support, please open a discussion at Curvy Community Portal or GitHub Issues
NOTE
Current limitations of Curvy SDK:
- New users cannot register their handles using Curvy SDK, only sign in is supported
- While Curvy SDK is in closed beta, API key is required to use the SDK
Curvy SDK is a Typescript SDK that works in both server and browser environments and gives you the complete feature set of the Curvy protocol.
Installation
Curvy SDK can be installed with a Node package manager:
pnpm install @0xcurvy/curvy-sdk
The easiest way to get started is to run the provided curvy-os
example app:
git clone https://github.com/0xCurvy/curvy-sdk.git
cd example/curvy-os
pnpm install
pnpm start
After opening the plain JS/HTML app in your browser, configure the API key you have received from the team, and you will have the complete set of features in the Curvy SDK at your disposal through the primitive UI of curvy-os
.
All code examples provided below are code snippets extracted from the curvy-os
example, and are written for the plain JavaScript clientside environment
Initializing the SDK
Setting up the API keys
Initializing the Curvy SDK is as simple as providing the API key, and the list of networks you want to support.
import { init } from "@0xcurvy/curvy-sdk";
window.curvySDK = await init(
{
apiKey: "YOURAPIKEY", // The API key you have received from the team after submitting the form.
},
"https://api.curvy.box/", // The API base url of the Curvy backend
selectedNetworkFilter // The selector for which networks you want the instance of the Curvy SDK to support.
)
The value of the selectedNetworkFilter
can be:
string
: slug format, e.g. "ethereum-sepolia"number
: Curvy ID of the network (as returned by https://api.curvy.box/currency/latest)callback
: Filter callback function that takes the type Network as argumentboolean
: Indicates whether we should connect to all mainnets (false) or all testnets (true)undefined
: We want to select all networks
The selectedNetworkFilter
will influence on which networks you can query balances, create new stealth addresses, and send funds on.
To verify that the SDK is correctly initialized you can check the networks enabled by the SDK:
const networks = await window.curvySDK.GetNetworks();
console.log(networks);
Announcement syncing
NOTE
Curvy SDK works by first indexing the entire set of announcements available in the Curvy protocol.
Announcements are made whenever a new stealth address transfer is occurring and hold information that can only be used by the recipient, anonymously, to derive the new stealth address.
Announcements are essential for the receiving side to be able to detect their new stealth addresses.
After checking that the SDK is initialized, the Curvy SDK will start indexing the announcements in the browser.
As this is an asynchronous process you will have to set up event listeners using the following helper functions:
onSyncStarted(...)
- the syncing process has startedonSyncProgress(...)
- the syncing process has indexed a single batch of announcementsonSyncComplete(...)
- the syncing process is completeonSyncError(...)
- error occurred during syncing
All the helper functions share the same function signature. To illustrate, if you wanted to notify the user when the announcements have successfully synced, you will do the following:
window.curvySDK.onSyncComplete((event) => {
alert("Announcements synced!");
});
TIP
Curvy SDK automatically fetches new announcements when they are detected. No polling needs to be done on your side.
Now that you have successfully initialized Curvy, we are ready to let the users sign in with their existing Curvy accounts.
Signing into Curvy
Curvy SDK currently supports logging in through:
window.ethereum
(e.g. Metamask)window.starknet_argentX
Ready Walletwindow.starknet_braavos
Braavos Wallet
The heavy lifting of the sign-in process is done by the SDK itself.
The sign-in flow is:
- User connects the desired wallet account
- User is prompted to enter their password
- User is prompted to sign a secret message
- Announcements that have previously been synced are now scanned to match stealth addresses for this specific user
IMPORTANT
The entirety of the sign-in process is done client-side, and no user credentials are ever leaving the browser!
The sign-in process simply derives the Curvy keypairs (read more about viewing and spending keys) from the provided signature and password.
The GetSignatureParamsForNetworkFlavour(...)
method will prepare the object that needs to be signed by the user to properly derive the key pairs:
const signingObject = await window.curvySDK.GetSignatureParamsForNetworkFlavour("evm", ownerAddress, password);
The AddWalletWithSignature(...)
method will derive the Curvy key pairs from the signature provided by user's wallet:
await window.curvySDK.AddWalletWithSignature(ownerAddress, rawSignature);
After the call to AddWalletWithSignature(...)
is finished, you can check the GetWallets()
method to see that it has properly been initialized with Curvy SDK:
const wallets = window.curvySDK.GetWallets();
console.log(wallets);
console.log(`${wallets[0].GetCurvyHandle()} has logged in!`) // this will print: `vitalik.curvy.name has logged in!`
Code example with Metamask
if (!window.ethereum) {
alert("Metamask not installed!");
return;
}
const provider = window.ethereum;
// Request account access
const accounts = await provider.request({
method: "eth_requestAccounts",
});
const ownerAddress = accounts[0];
// Prompt the user to enter their password
const password = await prompt("Please enter password");
// Prompt eth_signTypedData
const signingObject = await window.curvySDK.GetSignatureParamsForNetworkFlavour("evm", ownerAddress, password);
const params = [ownerAddress, signingObject];
const rawSignature = await window.ethereum.request({
method: "eth_signTypedData_v4",
params,
});
await window.curvySDK.AddWalletWithSignature(ownerAddress, rawSignature);
Code example with Argent / Braavos
let wallet;
if (walletName === "argent") {
wallet = window.starknet_argentX;
} else if (walletName === "braavos") {
wallet = window.starknet_braavos;
}
if (wallet === undefined) {
throw new Error(`Error connecting to Starknet wallet: ${walletName}`);
}
await wallet.enable();
const ownerAddress = wallet.account.address;
const password = await prompt("Please enter password");
const signingObject = await window.curvySDK.GetSignatureParamsForNetworkFlavour("starknet", ownerAddress, password);
const signature = await wallet.account.signMessage(signingObject);
await window.curvySDK.AddWalletWithSignature(ownerAddress, signature);
Scanning the announcements
Now that the wallet is properly is initialized with the SDK and the user is signed in - Curvy SDK will automatically start scanning the previously synced Announcements with the new user's key pairs to detect any stealth addresses that may be owned by them.
The scanning process is completely automatic and is triggered whenever a new batch of Announcements is indexed or when a new wallet is added to Curvy SDK.
Just like with the announcements, you might want to inform the user when the scanning process is complete by using the event listeners, or when a new stealth address is matched:
onScanProgress(...)
- the scanning process has progressed through a single batchonScanComplete(...)
- the scanning process is completeonScanMatch(...)
- the scanning process has detected a new stealth address for the walletonScanError(...)
- error occurred during scanning
The entirety of the matched stealth addresses are also available in the CurvyWallet
object:
const wallets = window.curvySDK.GetWallets();
/*
This example will print the wallets Curvy handle and the matched stealth addressses:
vitalik.curvy.name:
- 0xcdef...
- 0xabab...
- ...more stealth addresses
*/
for (const wallet of wallets) {
console.log(`${wallet.GetCurvyHandle()}:`)
for (const stealthAddress of wallet.stealthAddresses) {
console.log(`- ${stealthAddress.address}`);
}
}
Querying balances
Now that we have:
- The SDK initialized
- Announcements synced
- User signed in
- User's stealth addresses scanned from the announcements and their key pairs
We are ready to query the balances for the different networks and currencies that Curvy SDK supports.
NOTE
You can always refer to the https://api.curvy.box/currency/latest endpoint to see which currencies and networks are supported.
This is done as simply as:
await window.curvySDK.RefreshBalances();
The above asynchronous call will query the balances for all the networks matched by the selectedNetworkFilter
you provided when initializing the SDK, all supported currencies on those networks and all the wallets you have signed in to with Curvy SDK.
To display the balances, you can access the wallets registered with the SDK in the same manner as before:
/*
This example will print the wallets Curvy handlem, the matched stealth addressses and their balances:
vitalik.curvy.name:
- 0xcdef...
- 415 STRK @Starknet
- 32.1 ETH @Ethereum Sepolia
- 131 USDC @Ethereum Sepolia
- ...more stealth addresses and balances...
*/
for (const wallet of wallets) {
console.log(`${wallet.GetCurvyHandle()}:`)
for (const stealthAddress of wallet.stealthAddresses) {
console.log(`- ${stealthAddress.address}`);
for (const balanceIdentifier in stealthAddress.balances) {
const [network, currency] = window.curvySDK.GetNetworkAndCurrencyFromBalanceIdentifier(balanceIdentifier);
const balance = prettyPrintBalance(stealthAddress.balances[balanceIdentifier], currency.decimals);
const networkName = network.name;
const currencySymbol = currency.symbol;
console.log(`\t- ${balance} ${currencySymbol} @${networkName}`)
}
}
}
// Helper function that should be implemented by you that displays the balance in the desired format.
function prettyPrintBalance(amount, decimals, precision = 2) {
if (typeof amount !== "bigint") {
throw new TypeError("amount must be a BigInt");
}
const divisor = BigInt(10) ** BigInt(decimals);
const whole = amount / divisor;
const fraction = amount % divisor;
// Get fractional part as string with padding
let fractionStr = ((fraction * BigInt(10 ** precision)) / divisor).toString().padStart(precision, "0");
// Trim trailing zeros from fractional part
fractionStr = fractionStr.replace(/0+$/, "");
return fractionStr.length > 0 ? `${whole.toString()}.${fractionStr}` : whole.toString();
}
Sending funds
Sending funds from a stealth address is as simple as invoking a single function.
The below function will send the funds from a specific stealth address and console.log()
the link to the explorer.
const stealthAddress = window.curvySDK.GetStealthAddress("0xcdef...");
const network = "ethereum"
const toAddress = "..."
const amount = "0.13"
const currency = "USDC"
const txHash = await window.curvySDK.Send(
stealthAddress,
network,
toAddress,
amount,
currency
);
const { blockExplorerUrl } = await window.curvySDK.GetNetwork(network);
console.log(`${blockExplorerUrl}/tx/${txHash}`, "_blank");
TIP
To provide better UX to the user, you should always estimate the fees first and inform the user prior to running send. Take a look at the provided example in the curvy-os
to see how we first estimate the fees, and then use the same fees to send the transaction.