Quickstart
This guide walks you through creating an account and earning yield on Compound — the most common integration flow. By the end, you’ll have a working sub-account earning USDC yield on Base.
Prerequisites
- A Legend Prime Account with a query key (contact us to get set up)
- Node.js 18+ or Python 3.9+
1. Install the SDK
npm install legend-prime viem
2. Generate a signer key
Each sub-account needs a signer — an Ethereum key that authorizes fund movements. For this quickstart, we’ll generate a fresh EOA key:
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const privateKey = generatePrivateKey();
const signer = privateKeyToAccount(privateKey);
console.log("Private key:", privateKey); // Save this securely!
console.log("Signer address:", signer.address);
Save the private key. You’ll need it to sign every transaction for this account. If you lose it, funds in the account can’t be moved.
3. Initialize the client
import { LegendPrime } from "legend-prime";
const client = new LegendPrime({
queryKey: process.env.LEGEND_QUERY_KEY,
});
4. Create a sub-account
Pass the signer’s address to create a sub-account. Legend creates a Quark Wallet — an on-chain smart wallet — for the account.
curl -X POST https://prime-api.legend.xyz/accounts \
-H "Authorization: Bearer $LEGEND_QUERY_KEY" \
-H "Content-Type: application/json" \
-d '{
"signer_type": "eoa",
"signer_address": "'$SIGNER_ADDRESS'"
}'
The response includes two addresses:
signer_address — Your signing key’s address (what you generated in step 2)
legend_wallet_address — The on-chain smart wallet Legend created for this account
These are different. The wallet address is where funds live.
Fund the wallet, not the signer. To use this account, send assets to the legend_wallet_address on a supported network (Base, Ethereum Mainnet, Arbitrum, or Optimism). This is mainnet — start with a small amount (e.g., 1 USDC) to test your integration.
5. Create an earn plan
Once the wallet is funded, create a plan. Legend generates the on-chain transaction details and returns an EIP-712 digest for signing.
curl -X POST https://prime-api.legend.xyz/accounts/$ACCOUNT_ID/plan/earn \
-H "Authorization: Bearer $LEGEND_QUERY_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": "1000000",
"asset": "USDC",
"network": "base",
"protocol": "compound"
}'
Plans expire after 2 minutes. Sign and execute before expires_at or create a new plan.
6. Sign the plan
The signer approves the plan by signing the EIP-712 digest. This happens locally — the private key never leaves your infrastructure.
const digest = plan.details.eip712_data.digest;
const signature = await signer.sign({ hash: digest });
7. Execute the plan
Send the signature back to Legend. This triggers the on-chain transaction.
curl -X POST https://prime-api.legend.xyz/accounts/$ACCOUNT_ID/plan/execute \
-H "Authorization: Bearer $LEGEND_QUERY_KEY" \
-H "Content-Type: application/json" \
-d '{
"plan_id": "'"$PLAN_ID"'",
"signature": "'"$SIGNATURE"'"
}'
8. Verify the result
Poll the activities endpoint to confirm the transaction landed.
curl https://prime-api.legend.xyz/accounts/$ACCOUNT_ID/activities \
-H "Authorization: Bearer $LEGEND_QUERY_KEY"
For real-time updates instead of polling, use the events endpoint with poll=true for long-polling.
Full example
Here’s the complete flow in one script:
import { LegendPrime } from "legend-prime";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const client = new LegendPrime({
queryKey: process.env.LEGEND_QUERY_KEY,
});
// 1. Generate a signer
const privateKey = process.env.SIGNER_PRIVATE_KEY as `0x${string}` ?? generatePrivateKey();
const signer = privateKeyToAccount(privateKey);
// 2. Create account
const account = await client.accounts.create({
signerType: "eoa",
signerAddress: signer.address,
});
// 3. Fund account.legend_wallet_address with USDC on Base, then...
// 4. Create earn plan
const plan = await client.plan.earn(account.account_id, {
amount: "1000000",
asset: "USDC",
network: "base",
protocol: "compound",
});
// 5. Sign
const signature = await signer.sign({
hash: plan.details.eip712_data.digest,
});
// 6. Execute
await client.plan.execute(account.account_id, {
planId: plan.plan_id,
signature,
});
// 7. Poll for confirmation
for (let i = 0; i < 30; i++) {
const { activities } = await client.accounts.activities(account.account_id);
if (activities.length > 0 && activities[0].activity_status === "confirmed") {
console.log("Transaction confirmed!");
break;
}
await new Promise((r) => setTimeout(r, 2000));
}
Next steps