@tortoise-os/terrapin
v0.5.0
Published
Where tortoises meet the water - Playwright testing utilities for Sui blockchain dApps
Maintainers
Readme
@tortoise-os/terrapin 🐢💧
Where tortoises meet the water - Playwright testing utilities for Sui blockchain dApps
Terrapin is a specialized Playwright testing library for Sui blockchain applications. Just like a terrapin (freshwater turtle) thrives where land meets water, this library bridges TortoiseOS and Sui (水 - water), providing fixtures and helpers to simplify E2E testing of Sui wallet interactions, transactions, and dApp flows.
Why This Package?
Testing Sui dApps is different from Ethereum dApps:
- ❌ Synpress doesn't work - it's built for MetaMask/EVM wallets only
- ❌ No mature Sui-specific tools exist (as of 2025)
- ✅ This package fills the gap - purpose-built for Sui with @mysten/dapp-kit
Features
✅ Easy wallet connection testing - Connect/disconnect with one line ✅ Transaction helpers - Wait for transactions, check status ✅ Transaction Builders - Fluent API for complex multi-step transactions ✅ Event Listening - Wait for and verify blockchain events ✅ Network Mocking - Mock RPC responses for deterministic tests ✅ Network detection - Verify correct network (localnet/devnet/etc) ✅ Custom Matchers - Sui-specific assertions for cleaner tests ✅ Pre-configured Test Wallets - Realistic wallet presets (whale, normie, empty, etc) ✅ CI/CD Templates - Ready-to-use GitHub Actions & GitLab CI configs ✅ TypeScript support - Full type safety and IDE autocomplete ✅ Works with @mysten/dapp-kit - Uses official unsafe burner wallet ✅ Zero configuration - Just install and use ✅ Production-ready - Used in TortoiseOS DeFi suite
Installation
# bun (recommended)
bun add -D @tortoise-os/terrapin
# npm
npm install --save-dev @tortoise-os/terrapin
# pnpm
pnpm add -D @tortoise-os/terrapin
# yarn
yarn add -D @tortoise-os/terrapinPeer Dependencies
This package requires:
@playwright/test^1.40.0
Quick Start
1. Enable Unsafe Burner Wallet
In your app's wallet provider, enable the burner wallet for tests:
// app/providers.tsx or similar
import { WalletProvider } from '@mysten/dapp-kit';
const isTestEnvironment = process.env.NODE_ENV === 'test' ||
process.env.NEXT_PUBLIC_ENABLE_BURNER_WALLET === 'true';
export function Providers({ children }) {
return (
<WalletProvider enableUnsafeBurner={isTestEnvironment}>
{children}
</WalletProvider>
);
}2. Configure Playwright
Update your playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
// ... other config
webServer: {
command: 'bun run dev',
url: 'http://localhost:3000',
env: {
// Enable burner wallet for tests
NEXT_PUBLIC_ENABLE_BURNER_WALLET: 'true',
NEXT_PUBLIC_SUI_NETWORK: 'localnet',
},
},
});3. Write Your First Test
// tests/wallet.spec.ts
import { test, expect } from '@tortoise-os/terrapin';
test('should connect to Sui wallet', async ({ page, suiWallet }) => {
await page.goto('/');
// Connect to wallet
await suiWallet.connect();
// Verify connection
expect(await suiWallet.isConnected()).toBe(true);
// Get wallet address
const address = await suiWallet.getAddress();
expect(address).toMatch(/^0x[a-fA-F0-9]{40,}/);
});4. Run Tests
bunx playwright testAPI Reference
suiWallet.connect()
Connects to the Sui burner wallet.
await suiWallet.connect();Throws: Error if connection fails or button not found
suiWallet.disconnect()
Disconnects from the current wallet.
await suiWallet.disconnect();suiWallet.isConnected()
Checks if a wallet is currently connected.
const connected = await suiWallet.isConnected();
expect(connected).toBe(true);Returns: Promise<boolean>
suiWallet.getAddress()
Gets the currently connected wallet address.
const address = await suiWallet.getAddress();
console.log(address); // "0x1234..."Returns: Promise<string | null> - Address or null if not found
suiWallet.waitForTransaction(timeout?)
Waits for a transaction to complete.
// Click button that sends transaction
await page.click('[data-testid="send-button"]');
// Wait for it to complete (default 10s timeout)
await suiWallet.waitForTransaction();
// Or with custom timeout
await suiWallet.waitForTransaction(15000); // 15 secondsParameters:
timeout- Maximum wait time in milliseconds (default: 10000)
Throws: Error if transaction doesn't complete within timeout
suiWallet.getNetwork()
Gets the current network name.
const network = await suiWallet.getNetwork();
expect(network).toBe('localnet');Returns: Promise<string | null> - Network name or null
Usage Examples
Basic Wallet Connection
import { test, expect } from '@tortoise-os/terrapin';
test('wallet connection flow', async ({ page, suiWallet }) => {
await page.goto('/');
// Initially not connected
expect(await suiWallet.isConnected()).toBe(false);
// Connect wallet
await suiWallet.connect();
expect(await suiWallet.isConnected()).toBe(true);
// Disconnect
await suiWallet.disconnect();
expect(await suiWallet.isConnected()).toBe(false);
});Testing Transactions
test('should transfer tokens', async ({ page, suiWallet }) => {
await page.goto('/transfer');
await suiWallet.connect();
// Fill in transfer form
await page.fill('[data-testid="recipient"]', '0x123...');
await page.fill('[data-testid="amount"]', '100');
// Submit transaction
await page.click('[data-testid="transfer-button"]');
// Wait for it to complete
await suiWallet.waitForTransaction(15000);
// Verify success
await expect(page.getByText(/transfer successful/i)).toBeVisible();
});Testing Swap/DeFi Operations
test('should swap tokens', async ({ page, suiWallet }) => {
await page.goto('/swap');
await suiWallet.connect();
// Select tokens
await page.selectOption('[data-testid="token-in"]', 'SUI');
await page.selectOption('[data-testid="token-out"]', 'USDC');
// Enter amount
await page.fill('[data-testid="amount-in"]', '10');
// Execute swap
await page.click('[data-testid="swap-button"]');
// Wait for swap transaction
await suiWallet.waitForTransaction();
// Check balance updated
const balance = await page.textContent('[data-testid="usdc-balance"]');
expect(parseFloat(balance || '0')).toBeGreaterThan(0);
});Multi-Step User Flows
test('complete user journey', async ({ page, suiWallet }) => {
// Step 1: Connect wallet
await page.goto('/');
await suiWallet.connect();
// Step 2: Navigate to vault
await page.click('text=Vaults');
await expect(page).toHaveURL(/.*vaults/);
// Step 3: Deposit into vault
await page.click('[data-testid="vault-sui"]');
await page.fill('[data-testid="deposit-amount"]', '50');
await page.click('[data-testid="deposit-button"]');
await suiWallet.waitForTransaction();
// Step 4: Verify deposit
const shares = await page.textContent('[data-testid="vault-shares"]');
expect(parseFloat(shares || '0')).toBeGreaterThan(0);
// Step 5: Clean up - disconnect
await suiWallet.disconnect();
});Testing Network Detection
test('should be on correct network', async ({ page, suiWallet }) => {
await page.goto('/');
await suiWallet.connect();
const network = await suiWallet.getNetwork();
expect(network).toBe('localnet');
// Or check in UI
await expect(page.getByText(/localnet/i)).toBeVisible();
});Best Practices
1. Use Data Test IDs
Add data-testid attributes to your components for stable selectors:
// Good ✅
<button data-testid="connect-wallet">Connect</button>
<input data-testid="amount-input" />
// Avoid ❌
<button className="btn-primary">Connect</button>2. Wait for Page Load
Always wait for the page to be ready before interacting:
test.beforeEach(async ({ page }) => {
await page.goto('/', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('body');
});3. Clean Up After Tests
The package automatically disconnects wallets after tests, but you can add custom cleanup:
test.afterEach(async ({ suiWallet }) => {
// Additional cleanup if needed
if (await suiWallet.isConnected()) {
await suiWallet.disconnect();
}
});4. Handle Transaction Timeouts
Set appropriate timeouts for slow transactions:
// Default 10s might not be enough for complex operations
await suiWallet.waitForTransaction(30000); // 30 seconds for complex tx5. Use Test Networks
Always test on localnet or devnet, never mainnet:
// playwright.config.ts
env: {
NEXT_PUBLIC_SUI_NETWORK: 'localnet', // or 'devnet'
}Troubleshooting
"Connect wallet button not found"
Problem: Test fails to find connect button
Solution:
- Ensure your app has a visible "Connect Wallet" button
- Check the button text matches
/connect wallet/iregex - Wait for page to load:
await page.waitForSelector('button')
"Wallet connection failed"
Problem: connect() succeeds but isConnected() returns false
Solution:
- Verify
enableUnsafeBurner={true}is set in WalletProvider - Check
NEXT_PUBLIC_ENABLE_BURNER_WALLET=truein env - Increase wait time after connect:
await page.waitForTimeout(2000)
"Transaction never completes"
Problem: waitForTransaction() times out
Solution:
- Ensure Sui localnet is running:
sui start - Increase timeout:
waitForTransaction(30000) - Check if transaction actually started (look for errors in browser console)
"Address not found"
Problem: getAddress() returns null
Solution:
- Add
data-testid="wallet-address"to your address display component - Ensure address is rendered in the DOM after connection
- Check if address is abbreviated (e.g., "0x123...abc")
CI/CD Integration
GitHub Actions Example
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Install Playwright
run: bunx playwright install --with-deps chromium
- name: Install Sui CLI
run: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
cargo install --locked --git https://github.com/MystenLabs/sui.git --branch main sui
- name: Start Sui localnet
run: |
export PATH="$HOME/.cargo/bin:$PATH"
sui start &
sleep 10
- name: Build and test
run: |
bun run build
bun run test:e2e
env:
NEXT_PUBLIC_ENABLE_BURNER_WALLET: 'true'
NEXT_PUBLIC_SUI_NETWORK: 'localnet'
CI: true
- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-report
path: playwright-report/
retention-days: 30Advanced Features
Custom Matchers
Write cleaner, more readable tests with Sui-specific assertions:
import { expectWithMatchers as expect } from '@tortoise-os/terrapin';
test('wallet assertions', async ({ page, suiWallet }) => {
await page.goto('/');
// Assert wallet is connected
await expect(suiWallet).toHaveConnectedWallet();
// Assert wallet has balance
await expect(page).toHaveBalance('SUI', 100);
// Assert wallet address is shown
await expect(page).toShowWalletAddress('0x123...');
// Assert network is correct
await expect(page).toShowNetwork('localnet');
// Assert transaction completed
await page.click('[data-testid="send-button"]');
await expect(page).toHaveCompletedTransaction({ timeout: 15000 });
// Assert success/error messages
await expect(page).toShowSuccess('Transaction completed');
await expect(page).toShowError(); // Check for any error
});Available Matchers:
| Matcher | Description | Example |
|---------|-------------|---------|
| toHaveConnectedWallet() | Asserts wallet is connected | await expect(suiWallet).toHaveConnectedWallet() |
| toHaveBalance(token, amount) | Asserts balance for a token | await expect(page).toHaveBalance('SUI', 100) |
| toShowWalletAddress(address?) | Asserts wallet address is displayed | await expect(page).toShowWalletAddress() |
| toHaveCompletedTransaction(options?) | Asserts transaction completed | await expect(page).toHaveCompletedTransaction() |
| toShowNetwork(network) | Asserts network name is shown | await expect(page).toShowNetwork('localnet') |
| toShowError(message?) | Asserts error message is displayed | await expect(page).toShowError('Insufficient funds') |
| toShowSuccess(message?) | Asserts success message is displayed | await expect(page).toShowSuccess('Transfer complete') |
Pre-Configured Test Wallets
Use realistic wallet presets for different testing scenarios:
import { test, expect } from '@tortoise-os/terrapin';
import { TEST_WALLETS, getTestWallet, suiToMist, formatBalance } from '@tortoise-os/terrapin';
test('test with whale wallet', async ({ page }) => {
// Get a pre-configured whale wallet
const whale = getTestWallet('whale');
console.log(whale.name); // "Whale"
console.log(whale.balances.SUI); // 1,000,000,000,000,000 MIST (1M SUI)
console.log(formatBalance(whale.balances.SUI)); // "1,000,000 SUI"
});
test('test with poor wallet', async ({ page }) => {
const poor = getTestWallet('poor');
console.log(poor.balances.SUI); // 500,000,000 MIST (0.5 SUI)
console.log(poor.description); // "Wallet with minimal funds"
});
// Helper functions
test('balance calculations', async () => {
const amount = suiToMist(10); // Convert 10 SUI to MIST
expect(amount).toBe(10_000_000_000);
const sui = mistToSui(10_000_000_000); // Convert MIST to SUI
expect(sui).toBe(10);
const formatted = formatBalance(10_000_000_000); // "10 SUI"
expect(formatted).toBe('10 SUI');
});Available Wallet Presets:
| Wallet | SUI Balance | Use Case |
|--------|-------------|----------|
| whale | 1,000,000 SUI | Testing high-value transactions |
| normie | 100 SUI | Normal user testing |
| degen | 10,000 SUI | Testing risky DeFi operations |
| poor | 0.5 SUI | Testing insufficient balance scenarios |
| empty | 0 SUI | Testing zero balance edge cases |
| nftCollector | 1,000 SUI + NFTs | Testing NFT features |
| gasSponsor | 10,000 SUI | Testing gas sponsorship |
| staker | 50,000 SUI | Testing staking operations |
| multiToken | Multiple tokens | Testing multi-token scenarios |
CI/CD Templates
Get up and running with CI/CD in minutes using our ready-to-use templates:
GitHub Actions
# Copy template to your repo
cp node_modules/@tortoise-os/terrapin/templates/github-actions.yml .github/workflows/e2e.yml
# Commit and push
git add .github/workflows/e2e.yml
git commit -m "Add E2E testing workflow"
git pushFeatures:
- Multi-browser testing matrix
- Automatic Sui localnet setup
- Test report uploads
- GitHub Pages deployment
- PR comments with results
GitLab CI
# Copy template to your repo
cp node_modules/@tortoise-os/terrapin/templates/gitlab-ci.yml .gitlab-ci.yml
# Commit and push
git add .gitlab-ci.yml
git commit -m "Add E2E testing pipeline"
git pushFeatures:
- Multi-stage pipeline
- Caching for faster runs
- Artifact uploads
- Pages deployment
- JUnit reports
See templates/README.md for detailed customization options.
Roadmap & Feature Priorities
We've organized upcoming features by priority based on impact, effort, and ROI:
| Priority | Feature | Impact | Effort | ROI | Status | |----------|---------|--------|--------|-----|--------| | 🔥 P0 | Custom Matchers | ⭐⭐⭐⭐⭐ | Medium | ⚡⚡⚡⚡⚡ | ✅ Done | | 🔥 P0 | Pre-Configured Wallets | ⭐⭐⭐⭐⭐ | Low | ⚡⚡⚡⚡⚡ | ✅ Done | | 🔥 P0 | CI/CD Templates | ⭐⭐⭐⭐ | Low | ⚡⚡⚡⚡⚡ | ✅ Done | | 🚀 P1 | Transaction Builders | ⭐⭐⭐⭐⭐ | Medium | ⚡⚡⚡⚡⭐ | ✅ Done | | 🚀 P1 | Event Listening | ⭐⭐⭐⭐ | Medium | ⚡⚡⚡⚡ | ✅ Done | | 🚀 P1 | Network Mocking | ⭐⭐⭐⭐ | High | ⚡⚡⚡ | ✅ Done | | 💡 P2 | Visual Regression | ⭐⭐⭐ | High | ⚡⚡⚡ | 🤔 Considering | | 💡 P2 | Multi-Wallet Testing | ⭐⭐⭐⭐ | High | ⚡⚡ | 🤔 Considering | | 💡 P2 | Performance Benchmarking | ⭐⭐⭐ | Medium | ⚡⚡⚡ | 🤔 Considering | | 🎯 P3 | Component Testing | ⭐⭐ | High | ⚡⚡ | 💭 Future | | 🎯 P3 | Real Wallet Extensions | ⭐⭐⭐⭐ | Very High | ⚡ | 💭 Future |
Quick-Win Features (P0) - ✅ Completed
These provide the highest ROI with minimal effort:
✅ Custom Matchers
- Reduces test boilerplate by 50%+
- Makes tests more readable and maintainable
- Examples:
toHaveConnectedWallet(),toShowWalletAddress()
✅ Pre-Configured Wallets
- Instant test setup with realistic balances
- 9 wallet presets for different scenarios
- Helper functions:
suiToMist(),formatBalance()
✅ CI/CD Templates
- One-command CI/CD setup
- GitHub Actions and GitLab CI
- Includes Sui localnet and test reporting
High-Impact Features (P1) - ✅ Completed
✅ Transaction Builders
Build complex Sui transactions with a fluent, type-safe API:
import { test } from '@tortoise-os/terrapin';
import { createTransactionBuilder } from '@tortoise-os/terrapin';
test('complex transaction flow', async ({ page, suiWallet }) => {
await page.goto('/');
await suiWallet.connect();
const txBuilder = createTransactionBuilder(page, suiWallet);
// Build and execute a multi-step transaction
const digest = await txBuilder
.transfer('0x123...', 100_000_000) // Transfer 0.1 SUI
.split('0xabc...', [50_000_000, 50_000_000]) // Split coin
.merge('0xdef...', ['0x111...', '0x222...']) // Merge coins
.moveCall('0x2::coin::transfer', ['0x2::sui::SUI'], ['0xabc', '0x456'])
.execute();
console.log('Transaction digest:', digest);
// Or execute and wait for confirmations
await txBuilder.reset()
.transfer('0x789...', 200_000_000)
.executeAndWait(2); // Wait for 2 confirmations
});Features:
- Fluent, chainable API
- Type-safe transaction building
- Reduces manual PTB construction
- Supports transfer, split, merge, moveCall operations
- Custom transaction steps via
.custom()
✅ Event Listening
Wait for and verify blockchain events in your tests:
import { test } from '@tortoise-os/terrapin';
import { createEventListener } from '@tortoise-os/terrapin';
test('wait for transfer event', async ({ page, suiWallet }) => {
await page.goto('/');
await suiWallet.connect();
const listener = createEventListener(page);
// Execute transaction
await page.click('[data-testid="send-button"]');
// Wait for Transfer event
const event = await listener.waitForEvent('Transfer', {
timeout: 5000,
filter: (e) => e.parsedJson?.amount > 100_000_000
});
console.log('Transfer event:', event);
expect(event.sender).toBe('0x123...');
// Check all events
const allEvents = listener.getEvents();
console.log('Total events:', allEvents.length);
// Get specific events
const transfers = listener.getEvents('Transfer');
const lastTransfer = listener.getLastEvent('Transfer');
// Check if event occurred
expect(listener.hasEvent('Transfer')).toBe(true);
expect(listener.getEventCount('Transfer')).toBeGreaterThan(0);
});
test('register event callbacks', async ({ page, suiWallet }) => {
const listener = createEventListener(page);
// Register callback for specific event
listener.on('Transfer', (event) => {
console.log('Transfer detected:', event);
});
// Register wildcard callback for all events
listener.on('*', (event) => {
console.log('Event:', event.type);
});
// Execute actions...
await page.click('[data-testid="send-button"]');
// Wait for multiple events
const events = await listener.waitForEvents(['Transfer', 'ObjectCreated']);
expect(events).toHaveLength(2);
});Features:
- Wait for specific blockchain events
- Filter events with custom predicates
- Register event callbacks
- Track event history
- Support for wildcard listeners
✅ Network Mocking
Mock Sui RPC responses for faster, deterministic tests:
import { test } from '@tortoise-os/terrapin';
import { createNetworkMock } from '@tortoise-os/terrapin';
test('mock balance response', async ({ page }) => {
const mock = createNetworkMock(page);
// Mock balance for a specific address
mock.mockBalance('0x123...', 1_000_000_000); // 1 SUI
// Mock transaction result
mock.mockTransaction('0xabc123', 'success');
// Mock gas price
mock.mockGasPrice(2000);
// Enable mocking
await mock.enable();
// Now all RPC requests will be intercepted and mocked
await page.goto('/');
// Your app will see the mocked balance!
});
test('mock custom RPC responses', async ({ page }) => {
const mock = createNetworkMock(page);
// Mock any RPC method
mock.mockRpcResponse('sui_getBalance', {
totalBalance: '5000000000',
coinType: '0x2::sui::SUI',
coinObjectCount: 5
});
mock.mockRpcResponse('sui_getObject', {
objectId: '0xobj123',
owner: { AddressOwner: '0x123...' },
type: '0x2::coin::Coin<0x2::sui::SUI>'
});
await mock.enable();
await page.goto('/');
// Verify requests were intercepted
expect(mock.wasMethodCalled('sui_getBalance')).toBe(true);
expect(mock.getCallCount('sui_getBalance')).toBeGreaterThan(0);
const requests = mock.getInterceptedRequests('sui_getBalance');
console.log('Balance requests:', requests);
});
test('mock transaction failure', async ({ page }) => {
const mock = createNetworkMock(page);
// Mock a failed transaction
mock.mockTransactionFailure('Insufficient gas');
await mock.enable();
await page.goto('/');
// Test error handling...
});
test('mock with chaining', async ({ page }) => {
const mock = createNetworkMock(page);
// Chain multiple mocks
await mock
.mockBalance('0x123...', 1_000_000_000)
.mockTransaction('0xabc', 'success')
.mockGasPrice(1500)
.mockObject('0xobj', '0x123', '0x2::sui::SUI')
.enable();
await page.goto('/');
// Clean up
await mock.disable();
mock.clearMocks();
});Features:
- Mock any Sui RPC method
- Mock balances, transactions, objects, gas price
- Intercept and verify RPC requests
- Support for transaction failures
- Deterministic test behavior
- Offline testing support
Medium-Impact Features (P2) - 🤔 Considering
Visual Regression Testing
- Screenshot comparison for UI changes
- Wallet connection flow screenshots
- Transaction confirmation dialogs
Multi-Wallet Testing
- Test interactions between multiple wallets
- Transfer between accounts
- Multi-sig scenarios
Performance Benchmarking
- Measure transaction speed
- Track test execution time
- Identify performance regressions
Future Considerations (P3) - 💭 Long-term
Component Testing
- Test individual React components
- Mock wallet context
- Faster than E2E tests
Real Wallet Extensions
- Test with Sui Wallet browser extension
- Test Ethos/Martian wallets
- More realistic but complex setup
Comparison with Other Tools
| Feature | @tortoise-os/terrapin | Synpress | Manual Playwright | |---------|--------------------------|----------|-------------------| | Sui Support | ✅ Native | ❌ No | ⚠️ Manual | | Wallet Connection | ✅ One-liner | ❌ N/A | ⚠️ ~50 lines | | Transaction Handling | ✅ Built-in | ❌ N/A | ⚠️ Manual | | TypeScript | ✅ Full support | ✅ Yes | ✅ Yes | | Setup Complexity | ✅ Low | ❌ High | ⚠️ Medium | | CI-Friendly | ✅ Very | ⚠️ Moderate | ✅ Yes |
Requirements
- Node.js >= 18.0.0
- @playwright/test >= 1.40.0
- @mysten/dapp-kit >= 0.14.0 (in your app)
- Sui CLI (for localnet testing)
Changelog
v0.5.0 (2025-10-18)
- ✨ P1 Features Complete!
- ✨ Transaction Builders - Fluent API for complex transactions
- ✨ Event Listening - Wait for and verify blockchain events
- ✨ Network Mocking - Mock RPC responses for deterministic tests
- 📚 Comprehensive tests for all P1 features
- 📚 Updated documentation with P1 usage examples
v0.2.0 (2025-10-18)
- ✨ Custom Matchers for cleaner assertions
- ✨ Pre-configured test wallets (9 presets)
- ✨ CI/CD templates (GitHub Actions + GitLab CI)
- 📚 Expanded documentation with advanced features
- 🎯 Roadmap with feature priorities
v0.1.0 (2025-01-17)
- 🎉 Initial release
- ✅ Wallet connection helpers
- ✅ Transaction waiting
- ✅ Network detection
- ✅ Full TypeScript support
- ✅ Comprehensive documentation
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
Development Setup
# Clone repo
git clone https://github.com/yourusername/bun-move.git
cd bun-move
# Install dependencies
bun install
# Build package
cd packages/terrapin
bun run build
# Run tests
cd ../../apps/web
bun run test:e2eLicense
MIT © TortoiseOS Team
Support
Related Packages
- @tortoise-os/ui - Magic UI components for Sui dApps
- @tortoise-os/sdk - Sui SDK wrapper
- @tortoise-os/hooks - React hooks for Sui
About the Name
Terrapin (noun): A type of freshwater turtle that thrives where land meets water.
Just as a terrapin navigates both worlds with ease, @tortoise-os/terrapin seamlessly bridges TortoiseOS's steady, reliable approach with Sui's fluid blockchain technology (Sui 水 means "water" in Japanese).
The philosophy: Like the wise terrapin, we believe in steady, thorough testing - taking time to ensure quality while gracefully adapting to the flowing nature of blockchain development.
🐢 Slow, steady, and thorough - The terrapin way.
Made with ❤️ by the TortoiseOS team 🐢
