Initial commit

This commit is contained in:
defiQUG
2026-01-01 08:04:06 -08:00
commit d0bc005be1
75 changed files with 15082 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
# Chain 138 Smoke Tests Configuration
# Copy this file to .env and fill in your actual values
# Private key for test wallet (required for write operations)
# IMPORTANT: Never commit .env file with real private keys!
TEST_PRIVATE_KEY=your_private_key_here
# Optional: RPC endpoint override
# Default: https://138.rpc.thirdweb.com
TEST_RPC_URL=https://138.rpc.thirdweb.com
# Optional: Test recipient address for transfers
# Default: 0x0000000000000000000000000000000000000001
TEST_RECIPIENT=0x0000000000000000000000000000000000000001

View File

@@ -0,0 +1,49 @@
# Smoke Tests
End-to-end smoke tests for all Chain 138 offerings.
## Setup
1. Create `.env` file in this directory:
```bash
TEST_PRIVATE_KEY=your_private_key_here
TEST_RPC_URL=https://138.rpc.thirdweb.com # Optional
TEST_RECIPIENT=0x... # Optional, default test recipient
```
2. Install dependencies:
```bash
pnpm install
```
## Running Tests
Run all tests:
```bash
pnpm test
```
Run specific test suite:
```bash
pnpm test:wallets
pnpm test:x402
pnpm test:bridge
pnpm test:tokens
pnpm test:ai
pnpm test:http-api
```
## Test Coverage
- **Wallets**: Connect, sign message, native transfer, chain switching
- **x402**: Payment request creation, fulfillment, receipt verification, replay protection
- **Bridge**: Route validation, quote generation, token lists
- **Tokens**: ERC20 deploy, mint, transfer, balance queries
- **AI**: Read actions (balance, block height), write actions (transfer), prompt validation
- **HTTP API**: Client creation, configuration validation
## Notes
- Tests requiring write operations need `TEST_PRIVATE_KEY` set
- Some tests may skip if private key is not provided
- Tests use Chain 138 testnet/mainnet as configured

View File

@@ -0,0 +1,41 @@
{
"name": "smoke-tests",
"version": "0.1.0",
"description": "Smoke tests for Chain 138 offerings",
"type": "module",
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:wallets": "vitest run src/wallets",
"test:x402": "vitest run src/x402",
"test:bridge": "vitest run src/bridge",
"test:tokens": "vitest run src/tokens",
"test:ai": "vitest run src/ai",
"test:http-api": "vitest run src/http-api",
"test:all": "vitest run"
},
"keywords": [
"testing",
"smoke-tests",
"chain-138"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*",
"@dbis-thirdweb/wallets": "workspace:*",
"@dbis-thirdweb/x402": "workspace:*",
"@dbis-thirdweb/bridge": "workspace:*",
"@dbis-thirdweb/tokens": "workspace:*",
"@dbis-thirdweb/ai": "workspace:*",
"@dbis-thirdweb/http-api": "workspace:*",
"@thirdweb-dev/sdk": "^4.0.0",
"ethers": "^5.7.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.0.0",
"vitest": "^1.0.0"
}
}

View File

@@ -0,0 +1,90 @@
import { describe, it, expect } from 'vitest';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { ethers } from 'ethers';
import {
createReadBalanceAction,
createReadBlockHeightAction,
createTransferAction,
executeReadAction,
createReadPrompt,
validatePromptForChain138,
validateChainId,
} from '@dbis-thirdweb/ai';
import { chain138 } from '@dbis-thirdweb/chain';
import { testConfig } from '../config';
describe('AI Smoke Tests', () => {
it('should create read balance action', () => {
const action = createReadBalanceAction('0x1234567890123456789012345678901234567890');
expect(action.type).toBe('read');
expect(action.chainId).toBe(138);
expect(action.operation).toBe('getBalance');
});
it('should create read block height action', () => {
const action = createReadBlockHeightAction();
expect(action.type).toBe('read');
expect(action.chainId).toBe(138);
expect(action.operation).toBe('getBlockNumber');
});
it('should create transfer action', () => {
const action = createTransferAction(
'0x1234567890123456789012345678901234567890',
'1000000000000000000'
);
expect(action.type).toBe('write');
expect(action.chainId).toBe(138);
expect(action.operation).toBe('transferNative');
});
it('should execute read balance action', async () => {
if (!testConfig.privateKey) {
console.log('Skipping read balance test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const signer = await sdk.getSigner();
const address = await signer.getAddress();
const action = createReadBalanceAction(address);
const result = await executeReadAction(action, sdk, provider);
expect(result).toBeTruthy();
expect(typeof (result as { balance: string }).balance).toBe('string');
});
it('should execute read block height action', async () => {
if (!testConfig.privateKey) {
console.log('Skipping block height test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const action = createReadBlockHeightAction();
const result = await executeReadAction(action, sdk, provider);
expect(result).toBeTruthy();
expect((result as { blockNumber: number }).blockNumber).toBeGreaterThan(0);
});
it('should create chain-aware prompts', () => {
const prompt = createReadPrompt('getBalance', { address: '0x...' });
expect(prompt).toContain('Chain 138');
expect(prompt).toContain('138');
});
it('should validate prompts for Chain 138', () => {
const validPrompt = 'Perform operation on Chain 138';
expect(() => validatePromptForChain138(validPrompt)).not.toThrow();
});
it('should validate chain ID', () => {
expect(() => validateChainId(138)).not.toThrow();
expect(() => validateChainId(1)).toThrow();
});
});

View File

@@ -0,0 +1,69 @@
import { describe, it, expect } from 'vitest';
import { ethers } from 'ethers';
import {
getSupportedRoutes,
isRouteSupported,
generateBridgeQuote,
getAllBridgeableTokens,
} from '@dbis-thirdweb/bridge';
import { chain138 } from '@dbis-thirdweb/chain';
describe('Bridge Smoke Tests', () => {
it('should get supported routes to Chain 138', () => {
const routes = getSupportedRoutes();
expect(routes.length).toBeGreaterThan(0);
expect(routes.every((r) => r.toChainId === chain138.chainId)).toBe(true);
});
it('should check if route is supported', () => {
expect(isRouteSupported(1, 138)).toBe(true); // Ethereum to Chain 138
expect(isRouteSupported(138, 1)).toBe(false); // Chain 138 to Ethereum (not supported)
});
it('should get bridgeable tokens', () => {
const tokens = getAllBridgeableTokens();
expect(tokens.length).toBeGreaterThan(0);
const nativeToken = tokens.find((t) => t.isNative);
expect(nativeToken).toBeTruthy();
expect(nativeToken?.symbol).toBe('ETH');
});
it('should generate bridge quote', async () => {
const tokens = getAllBridgeableTokens();
const nativeToken = tokens.find((t) => t.isNative);
expect(nativeToken).toBeTruthy();
const quote = await generateBridgeQuote({
fromChainId: 1,
toChainId: 138,
token: nativeToken!,
amount: ethers.utils.parseEther('0.1'),
slippageBps: 50,
});
expect(quote.fromChainId).toBe(1);
expect(quote.toChainId).toBe(138);
expect(quote.token).toBe(nativeToken);
expect(quote.amount).toBe(ethers.utils.parseEther('0.1'));
expect(quote.estimatedOutput).toBeGreaterThan(0n);
expect(quote.fee).toBeGreaterThanOrEqual(0n);
expect(quote.minimumOutput).toBeLessThanOrEqual(quote.estimatedOutput);
});
it('should validate quote', async () => {
const tokens = getAllBridgeableTokens();
const nativeToken = tokens.find((t) => t.isNative);
expect(nativeToken).toBeTruthy();
const quote = await generateBridgeQuote({
fromChainId: 1,
toChainId: 138,
token: nativeToken!,
amount: ethers.utils.parseEther('0.1'),
});
// Quote should be valid
expect(quote.amount).toBeGreaterThan(0n);
expect(quote.estimatedOutput).toBeGreaterThan(0n);
});
});

View File

@@ -0,0 +1,26 @@
import 'dotenv/config';
/**
* Test configuration
*/
export const testConfig = {
/**
* Private key for test wallet (required for write operations)
* Set via TEST_PRIVATE_KEY environment variable
*/
privateKey: process.env.TEST_PRIVATE_KEY || '',
/**
* RPC endpoint override (optional)
*/
rpcUrl: process.env.TEST_RPC_URL,
/**
* Test recipient address (optional)
*/
testRecipient: process.env.TEST_RECIPIENT || '0x0000000000000000000000000000000000000001',
};
if (!testConfig.privateKey) {
console.warn('WARNING: TEST_PRIVATE_KEY not set. Write operations will fail.');
}

View File

@@ -0,0 +1,48 @@
import { describe, it, expect } from 'vitest';
import { createAPIClient, createChain138API } from '@dbis-thirdweb/http-api';
describe('HTTP API Smoke Tests', () => {
it('should create API client', () => {
const client = createAPIClient({
timeout: 30000,
retries: 3,
});
expect(client).toBeTruthy();
});
it('should create Chain 138 API client', () => {
const api = createChain138API({
timeout: 30000,
});
expect(api).toBeTruthy();
});
it('should have chain 138 configuration', () => {
const api = createChain138API();
// API client should be configured for Chain 138
expect(api).toBeTruthy();
});
// Note: Actual API endpoint tests would require:
// 1. Valid API key
// 2. Actual thirdweb API endpoints for Chain 138
// 3. Network access
// These are placeholders for when API is fully configured
it('should handle timeout configuration', () => {
const client = createAPIClient({
timeout: 5000,
});
expect(client).toBeTruthy();
});
it('should handle retry configuration', () => {
const client = createAPIClient({
retries: 5,
retryDelay: 2000,
});
expect(client).toBeTruthy();
});
});

View File

@@ -0,0 +1,11 @@
/**
* Unified smoke test runner for all Chain 138 offerings
* Run with: pnpm test
*/
export * from './wallets/index.test';
export * from './x402/index.test';
export * from './bridge/index.test';
export * from './tokens/index.test';
export * from './ai/index.test';
export * from './http-api/index.test';

View File

@@ -0,0 +1,106 @@
import { describe, it, expect } from 'vitest';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { ethers } from 'ethers';
import {
createTokenFactory,
deployERC20,
mintERC20,
transferERC20,
getERC20Balance,
} from '@dbis-thirdweb/tokens';
import { chain138 } from '@dbis-thirdweb/chain';
import { testConfig } from '../config';
describe('Tokens Smoke Tests', () => {
it('should create token factory', () => {
if (!testConfig.privateKey) {
console.log('Skipping factory test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const factory = createTokenFactory(sdk);
expect(factory).toBeTruthy();
});
it('should deploy ERC20 token', async () => {
if (!testConfig.privateKey) {
console.log('Skipping ERC20 deploy test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const contractAddress = await deployERC20(sdk, {
name: 'Test Token',
symbol: 'TEST',
description: 'Test token for smoke tests',
initialSupply: ethers.utils.parseEther('1000000'),
});
expect(contractAddress).toBeTruthy();
expect(ethers.isAddress(contractAddress)).toBe(true);
// Verify contract exists
const contract = await sdk.getContract(contractAddress, 'token');
const name = await contract.call('name');
expect(name).toBe('Test Token');
});
it('should mint ERC20 tokens', async () => {
if (!testConfig.privateKey) {
console.log('Skipping mint test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const signer = await sdk.getSigner();
const recipient = await signer.getAddress();
// Deploy token first
const contractAddress = await deployERC20(sdk, {
name: 'Mint Test Token',
symbol: 'MTT',
});
// Mint tokens
const amount = ethers.utils.parseEther('1000');
await mintERC20(sdk, contractAddress, amount, recipient);
// Check balance
const balance = await getERC20Balance(sdk, contractAddress, recipient);
expect(balance).toBeGreaterThanOrEqual(amount);
});
it('should transfer ERC20 tokens', async () => {
if (!testConfig.privateKey) {
console.log('Skipping transfer test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const signer = await sdk.getSigner();
const fromAddress = await signer.getAddress();
// Deploy and mint tokens
const contractAddress = await deployERC20(sdk, {
name: 'Transfer Test Token',
symbol: 'TTT',
initialSupply: ethers.utils.parseEther('10000'),
});
// Transfer tokens
const transferAmount = ethers.utils.parseEther('100');
await transferERC20(sdk, contractAddress, testConfig.testRecipient, transferAmount);
// Check recipient balance
const recipientBalance = await getERC20Balance(sdk, contractAddress, testConfig.testRecipient);
expect(recipientBalance).toBeGreaterThanOrEqual(transferAmount);
});
it('should handle token metadata', () => {
// Test metadata utilities exist
const { defaultMetadataConfig } = require('@dbis-thirdweb/tokens');
expect(defaultMetadataConfig).toBeTruthy();
expect(defaultMetadataConfig.ipfsGateway).toBeTruthy();
});
});

View File

@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { chain138, getWalletConfig } from '@dbis-thirdweb/wallets';
import { testConfig } from '../config';
describe('Wallets Smoke Tests', () => {
it('should get wallet config for Chain 138', () => {
const config = getWalletConfig();
expect(config.confirmationBlocks).toBeGreaterThan(0);
expect(config.rpcFailover.primary).toContain('138');
});
it('should create SDK instance with Chain 138', async () => {
if (!testConfig.privateKey) {
console.log('Skipping SDK test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const chainId = await sdk.getChainId();
expect(chainId).toBe(138);
});
it('should connect and get chain ID', async () => {
if (!testConfig.privateKey) {
console.log('Skipping connection test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const address = await sdk.wallet.getAddress();
expect(address).toBeTruthy();
const chainId = await sdk.getChainId();
expect(chainId).toBe(138);
});
it('should sign a message', async () => {
if (!testConfig.privateKey) {
console.log('Skipping sign message test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const message = 'Test message for Chain 138';
const signature = await sdk.wallet.sign(message);
expect(signature).toBeTruthy();
expect(signature.length).toBeGreaterThan(0);
});
it('should send native token transfer', async () => {
if (!testConfig.privateKey) {
console.log('Skipping native transfer test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const signer = await sdk.getSigner();
// Get initial balance
const fromAddress = await signer.getAddress();
const initialBalance = await provider.getBalance(fromAddress);
// Send small amount (0.001 ETH)
const amount = BigInt('1000000000000000'); // 0.001 ETH
const tx = await signer.sendTransaction({
to: testConfig.testRecipient,
value: amount,
});
expect(tx.hash).toBeTruthy();
// Wait for confirmation
const receipt = await provider.waitForTransaction(tx.hash, 1);
expect(receipt).toBeTruthy();
expect(receipt.status).toBe(1);
});
it('should verify chain switching utilities', () => {
// Verify chain138 export
expect(chain138).toBeTruthy();
expect(chain138.chainId).toBe(138);
expect(chain138.rpc.length).toBeGreaterThan(0);
});
});

View File

@@ -0,0 +1,116 @@
import { describe, it, expect } from 'vitest';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { ethers } from 'ethers';
import {
PayToAccessFlow,
InMemoryReplayProtectionStore,
type PaymentRequest,
} from '@dbis-thirdweb/x402';
import { chain138 } from '@dbis-thirdweb/chain';
import { testConfig } from '../config';
describe('x402 Smoke Tests', () => {
it('should create payment request', async () => {
if (!testConfig.privateKey) {
console.log('Skipping x402 test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const replayStore = new InMemoryReplayProtectionStore();
const flow = new PayToAccessFlow(provider, replayStore);
const request = await flow.createRequest({
amount: ethers.utils.parseEther('0.001'),
recipient: testConfig.testRecipient,
expiresInSeconds: 3600,
});
expect(request.requestId).toBeTruthy();
expect(request.amount).toBe(ethers.utils.parseEther('0.001'));
expect(request.recipient).toBe(testConfig.testRecipient);
});
it('should generate challenge from request', async () => {
if (!testConfig.privateKey) {
console.log('Skipping challenge test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const replayStore = new InMemoryReplayProtectionStore();
const flow = new PayToAccessFlow(provider, replayStore);
const request = await flow.createRequest({
amount: ethers.utils.parseEther('0.001'),
recipient: testConfig.testRecipient,
expiresInSeconds: 3600,
});
const challenge = flow.generateChallenge(request);
expect(challenge.request.requestId).toBe(request.requestId);
expect(challenge.nonce).toBeTruthy();
expect(challenge.message).toBeTruthy();
});
it('should fulfill payment and verify receipt', async () => {
if (!testConfig.privateKey) {
console.log('Skipping fulfillment test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const signer = await sdk.getSigner();
const replayStore = new InMemoryReplayProtectionStore();
const flow = new PayToAccessFlow(provider, replayStore);
// Create request
const request = await flow.createRequest({
amount: ethers.utils.parseEther('0.001'),
recipient: testConfig.testRecipient,
expiresInSeconds: 3600,
});
// Generate challenge
const challenge = flow.generateChallenge(request);
// Fulfill payment
const receipt = await flow.fulfillPayment(challenge, signer);
expect(receipt.txHash).toBeTruthy();
expect(receipt.requestId).toBe(request.requestId);
expect(receipt.blockNumber).toBeGreaterThan(0);
// Verify payment
const isValid = await flow.verifyPayment(receipt, request);
expect(isValid).toBe(true);
});
it('should prevent replay attacks', async () => {
if (!testConfig.privateKey) {
console.log('Skipping replay protection test - no private key');
return;
}
const sdk = new ThirdwebSDK(chain138, testConfig.privateKey);
const provider = sdk.getProvider();
const replayStore = new InMemoryReplayProtectionStore();
const flow = new PayToAccessFlow(provider, replayStore);
const request = await flow.createRequest({
amount: ethers.utils.parseEther('0.001'),
recipient: testConfig.testRecipient,
expiresInSeconds: 3600,
});
// First request should succeed
await expect(flow.createRequest({
amount: ethers.utils.parseEther('0.001'),
recipient: testConfig.testRecipient,
expiresInSeconds: 3600,
metadata: request.requestId, // Use same request ID to test replay protection
})).rejects.toThrow();
});
});

View File

@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../../packages/chain" },
{ "path": "../../packages/wallets" },
{ "path": "../../packages/x402" },
{ "path": "../../packages/bridge" },
{ "path": "../../packages/tokens" },
{ "path": "../../packages/ai" },
{ "path": "../../packages/http-api" }
]
}

View File

@@ -0,0 +1,10 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
testTimeout: 60000,
hookTimeout: 60000,
},
});