@canopyhub/canopy-sdk
Version:
TypeScript SDK for Canopy Protocol
318 lines (238 loc) • 8.4 kB
Markdown
# Canopy SDK
TypeScript SDK for integrating Canopy Protocol vaults into your dApp.
## Features
- 🏦 **Vault Operations** - Deposit and withdraw from Canopy vaults
- 💰 **Staking & Rewards** - Stake LP tokens and claim rewards
- 🔍 **View Functions** - Query vault data and user positions
- 🎯 **Simple API** - Auto-detects token types, handles complexity internally
- ⚡ **Movement Network** - Optimized for Movement mainnet
## Installation
```bash
npm install @canopyhub/canopy-sdk
# or
yarn add @canopyhub/canopy-sdk
# or
bun add @canopyhub/canopy-sdk
```
## Quick Start
```typescript
import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
import { CanopyClient } from "@canopyhub/canopy-sdk";
// Initialize Aptos client for Movement Network
const aptos = new Aptos(
new AptosConfig({
network: Network.CUSTOM,
fullnode: "https://mainnet.movementnetwork.xyz/v1",
})
);
// Create Canopy client
const canopyClient = new CanopyClient(aptos, {
network: "movement-mainnet", // optional, defaults to movement-mainnet
sentioApiKey: "your-sentio-api-key", // optional, for staking pool data
});
// Fetch available vaults
const vaults = await canopyClient.getVaults();
// Deposit into a vault
const depositPayload = await canopyClient.deposit(
"0x123...", // vault address
1000000000n // amount with decimals
);
// Submit transaction (using wallet adapter)
await signAndSubmitTransaction({ data: depositPayload });
```
## Core Functions
### Vault Operations
```typescript
// Deposit funds into a vault
const payload = await canopyClient.deposit(vaultAddress, amount);
// Withdraw from a vault
const payload = await canopyClient.withdraw(vaultAddress, shares);
// Get all vaults
const vaults = await canopyClient.getVaults();
// Get specific vault details
const vault = await canopyClient.getVault(vaultAddress);
```
### Staking Operations
```typescript
// Stake vault shares into reward pools
// stakingToken should be the vault's shares token (vault.sharesAsset)
// userAddress is optional for subscription checking
// poolAddresses is optional - provide specific pool addresses to skip auto-discovery
const payload = await canopyClient.stake(
stakingToken,
amount,
userAddress,
poolAddresses
);
// Unstake tokens (auto-detects coin vs FA based on format)
// Fungible Asset: "0x123..."
// Coin Type: "0x1::aptos_coin::AptosCoin"
const payload = await canopyClient.unstake(tokenAddress, amount);
// Claim all pending rewards for multiple staking tokens
const payload = await canopyClient.claimRewards([stakingToken1, stakingToken2]);
```
### View Functions
```typescript
// Get user's complete staking position
const position = await canopyClient.getUserStakingPosition(
userAddress,
stakingToken
);
// Returns: { totalStaked, subscribedPools, pendingRewards }
// Get user's staked balance only
const balance = await canopyClient.getUserStakedBalance(
userAddress,
stakingToken
);
// Get earned rewards for a specific pool
const earned = await canopyClient.getUserEarned(userAddress, pool, rewardToken);
```
## React Example
See the [examples/react](./examples/react) directory for a complete working application showing:
- Wallet connection with Aptos Wallet Adapter
- Depositing and withdrawing from vaults
- Vault selection and shares staking workflow
- Staking vault shares, unstaking, and claiming rewards
- Displaying vault shares balance, staked balance, and earned rewards
To run the example:
```bash
cd examples/react
npm install
npm run dev
```
## Decimal Handling
All amounts in the SDK use `bigint` with full decimal precision. You need to scale your values:
```typescript
// Helper functions for decimal conversion
function scaleToDecimals(amount: string, decimals: number): bigint {
const [whole, fraction = ""] = amount.split(".");
const paddedFraction = fraction.padEnd(decimals, "0").slice(0, decimals);
return BigInt(whole + paddedFraction);
}
function scaleFromDecimals(amount: bigint, decimals: number): string {
const str = amount.toString().padStart(decimals + 1, "0");
const whole = str.slice(0, -decimals) || "0";
const fraction = str.slice(-decimals).replace(/0+$/, "");
return fraction ? `${whole}.${fraction}` : whole;
}
// Example: Deposit 10.5 tokens (with 8 decimals)
const amount = scaleToDecimals("10.5", 8); // 1050000000n
const payload = await canopyClient.deposit(vaultAddress, amount);
```
## Staking Pool Resolution
The SDK uses a multi-layer approach to find staking pools for your tokens, ensuring reliability even without API access:
### Layer 1: Direct Pool Addresses
```typescript
// Provide specific pool addresses directly
const poolAddresses = ["0xpool1...", "0xpool2..."];
const payload = await canopyClient.stake(
stakingToken,
amount,
userAddress,
poolAddresses
);
```
### Layer 2: Static Mapping Fallback
Built-in mappings for common staking tokens. The SDK automatically checks these when no pool addresses are provided.
### Layer 3: GraphQL API (Requires API Key)
```typescript
const canopyClient = new CanopyClient(aptos, {
sentioApiKey: "your-sentio-api-key", // For dynamic pool discovery
});
```
### Layer 4: Error Messages
If all layers fail, you'll get a error explaining your options:
```
"No staking pools found for token. Options: 1) Provide poolAddresses parameter, 2) Ensure staking token is in static mapping, 3) Provide sentioApiKey for dynamic lookup"
```
**For Production Use:** We recommend using Layer 1 (direct pool addresses) for the most reliable and fastest staking operations.
## External Data Integration
The SDK provides on-chain data. For complete dApp integration, combine with external APIs for:
- **APY/APR** - Real-time yield calculations
- **TVL in USD** - Vault total value locked in dollars
- **Token Prices** - For displaying USD values
Calculate user's vault position value:
```typescript
// Get on-chain data
const vault = await canopyClient.getVault(vaultAddress);
const userShares = await getUserVaultShares(userAddress, vaultAddress); // You need to implement this
// Get external data
const tvlUSD = await fetchTVLFromAPI(vaultAddress);
// Calculate user's position value
const userValueUSD = (userShares / vault.totalSupply) * tvlUSD;
```
## Transaction Submission
The SDK returns transaction payloads. Submit them using your preferred method:
### With Aptos Wallet Adapter (React)
```typescript
import { useWallet } from "@aptos-labs/wallet-adapter-react";
const { signAndSubmitTransaction } = useWallet();
const payload = await canopyClient.deposit(vaultAddress, amount);
const response = await signAndSubmitTransaction({ data: payload });
```
### Direct with Aptos SDK
```typescript
const payload = await canopyClient.deposit(vaultAddress, amount);
const transaction = await aptos.transaction.build.simple({
sender: account.address,
data: payload,
});
const response = await aptos.signAndSubmitTransaction({
signer: account,
transaction,
});
```
## Error Handling
The SDK throws `CanopyError` with specific error codes:
```typescript
import { CanopyError, CanopyErrorCode } from "@canopyhub/canopy-sdk";
try {
await canopyClient.deposit(vaultAddress, amount);
} catch (error) {
if (error instanceof CanopyError) {
switch (error.code) {
case CanopyErrorCode.VAULT_NOT_FOUND:
console.error("Vault doesn't exist");
break;
case CanopyErrorCode.AMOUNT_TOO_SMALL:
console.error("Amount must be greater than zero");
break;
case CanopyErrorCode.TRANSACTION_BUILD_FAILED:
console.error("Failed to build transaction");
break;
case CanopyErrorCode.NETWORK_ERROR:
console.error("Network or API error");
break;
case CanopyErrorCode.STAKING_POOLS_NOT_FOUND:
console.error(
"No staking pools found - check pool addresses or API key"
);
break;
}
} else {
// Handle GraphQL API errors, network timeouts, etc.
console.error("Unexpected error:", error.message);
}
}
```
## Requirements
- Node.js 20+
- TypeScript 4.5+
- `@aptos-labs/ts-sdk` ^4.0.0
## Development
```bash
# Install dependencies
yarn install
# Build the SDK
yarn build
# Run in development mode
yarn dev
# Type check
yarn typecheck
# Lint
yarn lint
```
## Support
- [GitHub Issues](https://github.com/Canopyxyz/canopy-sdk/issues)
## License
MIT