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

66
packages/ai/README.md Normal file
View File

@@ -0,0 +1,66 @@
# @dbis-thirdweb/ai
Chain-aware AI prompts and actions for Chain 138.
## Usage
### Prompt Generation
```typescript
import { createReadPrompt, createWritePrompt, validatePromptForChain138 } from '@dbis-thirdweb/ai';
// Create read prompt
const readPrompt = createReadPrompt('getBalance', { address: '0x...' });
// Create write prompt
const writePrompt = createWritePrompt('transferNative', {
to: '0x...',
amount: '1000000000000000000', // 1 ETH in wei
});
// Validate prompt targets Chain 138
validatePromptForChain138(userPrompt);
```
### Read Actions
```typescript
import {
createReadBalanceAction,
createReadBlockHeightAction,
executeReadAction,
} from '@dbis-thirdweb/ai';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { chain138 } from '@dbis-thirdweb/chain';
const sdk = new ThirdwebSDK(chain138);
const provider = sdk.getProvider();
// Create action
const action = createReadBalanceAction('0x...');
// Execute
const result = await executeReadAction(action, sdk, provider);
console.log(result); // { address, balance, balanceFormatted }
```
### Write Actions
```typescript
import { createTransferAction, executeWriteAction } from '@dbis-thirdweb/ai';
// Create transfer action
const action = createTransferAction('0x...', '1000000000000000000');
// Execute (requires signer)
const result = await executeWriteAction(action, sdk);
console.log(result); // { txHash, chainId }
```
## Features
- Chain-aware prompt templates with Chain 138 guardrails
- Read action templates (balance, block height, token info)
- Write action templates (transfers, contract interactions)
- Chain ID routing validation
- Error handling for unsupported operations

38
packages/ai/package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "@dbis-thirdweb/ai",
"version": "0.1.0",
"description": "Chain-aware AI prompts and actions for Chain 138",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"ai",
"chain-138",
"prompts"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*",
"@thirdweb-dev/sdk": "^4.0.0",
"ethers": "^5.7.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

179
packages/ai/src/actions.ts Normal file
View File

@@ -0,0 +1,179 @@
import type { ThirdwebSDK } from '@thirdweb-dev/sdk';
import type { providers } from 'ethers';
import { chain138 } from '@dbis-thirdweb/chain';
import type { ReadAction, WriteAction } from './types';
import { validateChainId } from './types';
/**
* Read action templates
*/
/**
* Read balance action template
*/
export function createReadBalanceAction(address: string): ReadAction {
return {
type: 'read',
description: `Read native token balance for address ${address}`,
chainId: chain138.chainId,
operation: 'getBalance',
params: { address },
};
}
/**
* Read block height action template
*/
export function createReadBlockHeightAction(): ReadAction {
return {
type: 'read',
description: 'Read current block height',
chainId: chain138.chainId,
operation: 'getBlockNumber',
params: {},
};
}
/**
* Read token info action template
*/
export function createReadTokenInfoAction(contractAddress: string): ReadAction {
return {
type: 'read',
description: `Read token information for contract ${contractAddress}`,
chainId: chain138.chainId,
operation: 'getTokenInfo',
params: { contractAddress },
};
}
/**
* Execute read action
*/
export async function executeReadAction(
action: ReadAction,
sdk: ThirdwebSDK,
provider: providers.Provider
): Promise<unknown> {
validateChainId(action.chainId);
switch (action.operation) {
case 'getBalance': {
const address = action.params.address as string;
const balance = await provider.getBalance(address);
return {
address,
balance: balance.toString(),
balanceFormatted: `${(Number(balance) / 1e18).toFixed(4)} ${chain138.nativeCurrency.symbol}`,
};
}
case 'getBlockNumber': {
const blockNumber = await provider.getBlockNumber();
return {
blockNumber,
chainId: chain138.chainId,
};
}
case 'getTokenInfo': {
const contractAddress = action.params.contractAddress as string;
try {
const contract = await sdk.getContract(contractAddress);
// Attempt to read token info if it's a token contract
const [name, symbol, decimals] = await Promise.all([
contract.call('name').catch(() => null),
contract.call('symbol').catch(() => null),
contract.call('decimals').catch(() => null),
]);
return {
contractAddress,
name: name || 'Unknown',
symbol: symbol || 'Unknown',
decimals: decimals || 18,
};
} catch (error) {
throw new Error(`Failed to get token info: ${error}`);
}
}
default:
throw new Error(`Unknown read operation: ${action.operation}`);
}
}
/**
* Write action templates
*/
/**
* Create transfer action template
*/
export function createTransferAction(
to: string,
amount: string,
tokenAddress?: string
): WriteAction {
return {
type: 'write',
description: tokenAddress
? `Transfer ${amount} tokens from ${tokenAddress} to ${to}`
: `Transfer ${amount} ${chain138.nativeCurrency.symbol} to ${to}`,
chainId: chain138.chainId,
operation: tokenAddress ? 'transferToken' : 'transferNative',
params: {
to,
amount,
tokenAddress,
},
};
}
/**
* Execute write action
*/
export async function executeWriteAction(
action: WriteAction,
sdk: ThirdwebSDK
): Promise<{ txHash: string; chainId: number }> {
validateChainId(action.chainId);
const signer = await sdk.getSigner();
if (!signer) {
throw new Error('No signer available for write operations');
}
switch (action.operation) {
case 'transferNative': {
const to = action.params.to as string;
const amount = action.params.amount as string;
const tx = await signer.sendTransaction({
to,
value: BigInt(amount),
});
return {
txHash: tx.hash,
chainId: chain138.chainId,
};
}
case 'transferToken': {
const to = action.params.to as string;
const amount = action.params.amount as string;
const tokenAddress = action.params.tokenAddress as string;
const contract = await sdk.getContract(tokenAddress, 'token');
const tx = await contract.erc20.transfer(to, amount);
return {
txHash: tx.receipt.transactionHash,
chainId: chain138.chainId,
};
}
default:
throw new Error(`Unknown write operation: ${action.operation}`);
}
}

3
packages/ai/src/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * from './types';
export * from './prompts';
export * from './actions';

View File

@@ -0,0 +1,88 @@
import { chain138 } from '@dbis-thirdweb/chain';
import type { AIAction } from './types';
import { validateChainId } from './types';
/**
* Chain-aware prompt templates with Chain 138 guardrails
*/
/**
* Base prompt prefix for Chain 138 operations
*/
const CHAIN_138_PREFIX = `You are operating on Chain 138 (ChainID: ${chain138.chainId}).
All operations must be performed on Chain 138 only.
RPC endpoint: ${chain138.rpc[0] || 'https://138.rpc.thirdweb.com'}
Native currency: ${chain138.nativeCurrency.symbol}
`;
/**
* Create chain-aware prompt for read operations
*/
export function createReadPrompt(operation: string, params: Record<string, unknown>): string {
return `${CHAIN_138_PREFIX}
Perform a read operation on Chain 138:
Operation: ${operation}
Parameters: ${JSON.stringify(params, null, 2)}
Return the result in a structured format.`;
}
/**
* Create chain-aware prompt for write operations
*/
export function createWritePrompt(operation: string, params: Record<string, unknown>): string {
return `${CHAIN_138_PREFIX}
Perform a write operation on Chain 138:
Operation: ${operation}
Parameters: ${JSON.stringify(params, null, 2)}
IMPORTANT: Verify the chain ID is ${chain138.chainId} before executing.
Return the transaction hash upon success.`;
}
/**
* Create prompt from AI action
*/
export function createPromptFromAction(action: AIAction): string {
// Validate chain ID
validateChainId(action.chainId);
if (action.type === 'read') {
return createReadPrompt(action.operation, action.params);
} else {
return createWritePrompt(action.operation, action.params);
}
}
/**
* Extract chain ID from natural language prompt (safety check)
*/
export function extractChainIdFromPrompt(prompt: string): number | null {
// Look for explicit chain ID mentions
const chainIdMatch = prompt.match(/chain[_\s]?id[:\s]+(\d+)/i);
if (chainIdMatch) {
return parseInt(chainIdMatch[1], 10);
}
// Look for "Chain 138" mention
if (prompt.match(/chain[_\s]?138/i)) {
return chain138.chainId;
}
return null;
}
/**
* Guardrail: Ensure prompt targets Chain 138
*/
export function validatePromptForChain138(prompt: string): void {
const extractedChainId = extractChainIdFromPrompt(prompt);
if (extractedChainId && extractedChainId !== chain138.chainId) {
throw new Error(
`Prompt targets chain ${extractedChainId}, but this module is configured for Chain 138 (${chain138.chainId})`
);
}
}

76
packages/ai/src/types.ts Normal file
View File

@@ -0,0 +1,76 @@
import { chain138 } from '@dbis-thirdweb/chain';
/**
* AI action types
*/
export type AIActionType = 'read' | 'write';
/**
* Base AI action
*/
export interface BaseAIAction {
/**
* Action type (read or write)
*/
type: AIActionType;
/**
* Description of the action
*/
description: string;
/**
* Chain ID (should be Chain 138)
*/
chainId: number;
}
/**
* Read action (query blockchain data)
*/
export interface ReadAction extends BaseAIAction {
type: 'read';
/**
* What to read (e.g., "balance", "block height", "token info")
*/
operation: string;
/**
* Parameters for the read operation
*/
params: Record<string, unknown>;
}
/**
* Write action (send transaction)
*/
export interface WriteAction extends BaseAIAction {
type: 'write';
/**
* Transaction description
*/
operation: string;
/**
* Transaction parameters
*/
params: Record<string, unknown>;
/**
* Estimated gas cost (optional)
*/
estimatedGas?: bigint;
}
export type AIAction = ReadAction | WriteAction;
/**
* Validate chain ID is Chain 138
*/
export function validateChainId(chainId: number): void {
if (chainId !== chain138.chainId) {
throw new Error(
`Invalid chain ID: ${chainId}. Expected Chain 138 (${chain138.chainId})`
);
}
}

12
packages/ai/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../chain" }
]
}

58
packages/bridge/README.md Normal file
View File

@@ -0,0 +1,58 @@
# @dbis-thirdweb/bridge
Bridge routes and execution for Chain 138.
## Usage
### Get Supported Routes
```typescript
import { getSupportedRoutes, isRouteSupported } from '@dbis-thirdweb/bridge';
const routes = getSupportedRoutes();
const isSupported = isRouteSupported(1, 138); // Ethereum to Chain 138
```
### Generate Bridge Quote
```typescript
import { generateBridgeQuote, getAllBridgeableTokens } from '@dbis-thirdweb/bridge';
import { ethers } from 'ethers';
const tokens = getAllBridgeableTokens();
const nativeToken = tokens.find(t => t.isNative);
const quote = await generateBridgeQuote({
fromChainId: 1,
toChainId: 138,
token: nativeToken!,
amount: ethers.utils.parseEther('0.1'),
slippageBps: 50, // 0.5%
});
```
### Execute Bridge
```typescript
import { executeBridge, getBridgeStatus } from '@dbis-thirdweb/bridge';
const status = await executeBridge(quote, signer, provider);
// Poll for status
const finalStatus = await getBridgeStatus(status.sourceTxHash, provider);
```
## Features
- Canonical chain mapping for Chain 138
- Supported routes configuration
- Token lists (native, wrapped, stablecoins)
- Quote generation with slippage protection
- Bridge execution helpers
- Status tracking and finality checks
## Notes
- This implementation uses simplified bridge logic
- In production, integrate with thirdweb Bridge SDK or bridge provider APIs
- Token addresses need to be configured for Chain 138's actual deployed tokens

View File

@@ -0,0 +1,38 @@
{
"name": "@dbis-thirdweb/bridge",
"version": "0.1.0",
"description": "Bridge routes and execution for Chain 138",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"bridge",
"chain-138",
"cross-chain"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*",
"@thirdweb-dev/sdk": "^4.0.0",
"ethers": "^5.7.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,90 @@
import type { Signer, providers } from 'ethers';
import type { BridgeQuote, BridgeStatus } from './types';
import { validateQuote } from './quote';
/**
* Execute bridge transaction
* Note: This is a simplified implementation. In production, you'd use thirdweb Bridge SDK
* or a bridge provider's SDK/API
*/
export async function executeBridge(
quote: BridgeQuote,
signer: Signer,
provider: providers.Provider
): Promise<BridgeStatus> {
// Validate quote
if (!validateQuote(quote)) {
throw new Error('Invalid bridge quote');
}
// In production, this would:
// 1. Use thirdweb Bridge SDK: await bridge.bridge(quote)
// 2. Or call bridge provider API
// 3. Or interact with bridge contract directly
// Simplified implementation - send native transfer as placeholder
// This should be replaced with actual bridge contract interaction
const tx = await signer.sendTransaction({
to: quote.token.address,
value: quote.amount,
// Bridge contract would have specific data/contract interaction here
});
// Wait for transaction
const receipt = await provider.waitForTransaction(tx.hash, 1);
if (!receipt || receipt.status !== 1) {
throw new Error('Bridge transaction failed');
}
return {
sourceTxHash: receipt.transactionHash,
status: 'processing', // Would track through bridge provider to get final status
step: 'Transaction confirmed on source chain',
sourceBlockNumber: receipt.blockNumber,
};
}
/**
* Get bridge status (polling for destination chain confirmation)
*/
export async function getBridgeStatus(
sourceTxHash: string,
provider: providers.Provider
): Promise<BridgeStatus> {
// In production, this would query the bridge provider API or monitor on-chain events
const receipt = await provider.getTransactionReceipt(sourceTxHash);
if (!receipt) {
return {
sourceTxHash,
status: 'pending',
step: 'Waiting for source chain confirmation',
};
}
// Simplified - in production, you'd check bridge provider for destination chain status
return {
sourceTxHash,
status: receipt.status === 1 ? 'processing' : 'failed',
step: receipt.status === 1
? 'Processing bridge - waiting for destination chain'
: 'Bridge transaction failed',
sourceBlockNumber: receipt.blockNumber,
};
}
/**
* Check finality threshold for Chain 138
*/
export const FINALITY_THRESHOLD_BLOCKS = 1; // Adjust based on Chain 138's finality rules
/**
* Check if bridge transaction has reached finality
*/
export async function checkFinality(
blockNumber: number,
currentBlockNumber: number
): Promise<boolean> {
return currentBlockNumber >= blockNumber + FINALITY_THRESHOLD_BLOCKS;
}

View File

@@ -0,0 +1,5 @@
export * from './types';
export * from './routes';
export * from './tokenLists';
export * from './quote';
export * from './execution';

View File

@@ -0,0 +1,75 @@
import type { providers } from 'ethers';
import { ethers } from 'ethers';
import type { BridgeQuote, BridgeableToken } from './types';
import { isRouteSupported } from './routes';
import { chain138 } from '@dbis-thirdweb/chain';
/**
* Generate bridge quote
* Note: This is a simplified implementation. In production, you'd call a bridge provider API
*/
export async function generateBridgeQuote(params: {
fromChainId: number;
toChainId: number;
token: BridgeableToken;
amount: bigint;
provider?: providers.Provider;
slippageBps?: number; // Basis points (e.g., 100 = 1%)
}): Promise<BridgeQuote> {
const { fromChainId, toChainId, token, amount, slippageBps = 50 } = params;
// Validate route
if (!isRouteSupported(fromChainId, toChainId)) {
throw new Error(`Bridge route from ${fromChainId} to ${toChainId} is not supported`);
}
// Simplified quote calculation
// In production, this would call a bridge provider API (e.g., thirdweb Bridge API, Socket, etc.)
const bridgeFeeBps = 10; // 0.1% fee (simplified)
const fee = (amount * BigInt(bridgeFeeBps)) / BigInt(10000);
const estimatedOutput = amount - fee;
// Apply slippage protection
const minimumOutput = (estimatedOutput * BigInt(10000 - slippageBps)) / BigInt(10000);
// Estimated time (simplified - actual time depends on bridge provider and chains)
const estimatedTimeSeconds = 300; // 5 minutes default
return {
fromChainId,
toChainId,
token,
amount,
estimatedOutput,
fee,
estimatedTimeSeconds,
minimumOutput,
};
}
/**
* Validate bridge quote
*/
export function validateQuote(quote: BridgeQuote): boolean {
const amount = typeof quote.amount === 'bigint' ? quote.amount : BigInt(quote.amount.toString());
const estimatedOutput = typeof quote.estimatedOutput === 'bigint' ? quote.estimatedOutput : BigInt(quote.estimatedOutput.toString());
const minimumOutput = typeof quote.minimumOutput === 'bigint' ? quote.minimumOutput : BigInt(quote.minimumOutput.toString());
if (amount <= 0n) {
return false;
}
if (estimatedOutput <= 0n) {
return false;
}
if (minimumOutput > estimatedOutput) {
return false;
}
if (!isRouteSupported(quote.fromChainId, quote.toChainId)) {
return false;
}
return true;
}

View File

@@ -0,0 +1,59 @@
import { chain138 } from '@dbis-thirdweb/chain';
import type { BridgeRoute, BridgeableToken } from './types';
/**
* Native token for Chain 138
*/
export const chain138NativeToken: BridgeableToken = {
address: '0x0000000000000000000000000000000000000000',
symbol: 'ETH',
name: 'Ether',
decimals: 18,
isNative: true,
};
/**
* Canonical chain mapping: supported source chains for bridging to Chain 138
* Add chain IDs that you want to support bridging FROM
*/
export const SUPPORTED_SOURCE_CHAINS = [
1, // Ethereum Mainnet
5, // Goerli
137, // Polygon
80001, // Mumbai
// Add more chains as needed
];
/**
* Get supported bridge routes TO Chain 138
*/
export function getSupportedRoutes(): BridgeRoute[] {
return SUPPORTED_SOURCE_CHAINS.map((fromChainId) => ({
fromChainId,
toChainId: chain138.chainId,
tokens: [chain138NativeToken], // Add more tokens as needed
active: true,
}));
}
/**
* Check if a route is supported
*/
export function isRouteSupported(fromChainId: number, toChainId: number): boolean {
return (
toChainId === chain138.chainId &&
SUPPORTED_SOURCE_CHAINS.includes(fromChainId)
);
}
/**
* Get route for specific chain pair
*/
export function getRoute(fromChainId: number, toChainId: number): BridgeRoute | null {
if (!isRouteSupported(fromChainId, toChainId)) {
return null;
}
const routes = getSupportedRoutes();
return routes.find((r) => r.fromChainId === fromChainId) || null;
}

View File

@@ -0,0 +1,50 @@
import type { BridgeableToken } from './types';
import { chain138NativeToken } from './routes';
/**
* Token lists for Chain 138 bridging
*/
/**
* Native and wrapped tokens
*/
export const nativeTokens: BridgeableToken[] = [
chain138NativeToken,
// Add WETH if it exists on Chain 138
// {
// address: '0x...',
// symbol: 'WETH',
// name: 'Wrapped Ether',
// decimals: 18,
// isNative: false,
// },
];
/**
* Stablecoins (add actual addresses when available)
*/
export const stablecoins: BridgeableToken[] = [
// Example structure - fill in with actual Chain 138 token addresses
// {
// address: '0x...',
// symbol: 'USDC',
// name: 'USD Coin',
// decimals: 6,
// isNative: false,
// },
];
/**
* All bridgeable tokens for Chain 138
*/
export function getAllBridgeableTokens(): BridgeableToken[] {
return [...nativeTokens, ...stablecoins];
}
/**
* Find token by address
*/
export function findToken(address: string): BridgeableToken | undefined {
const tokens = getAllBridgeableTokens();
return tokens.find((token) => token.address.toLowerCase() === address.toLowerCase());
}

View File

@@ -0,0 +1,141 @@
import type { BigNumberish } from 'ethers';
/**
* Supported token for bridging
*/
export interface BridgeableToken {
/**
* Token address (native token uses zero address)
*/
address: string;
/**
* Token symbol
*/
symbol: string;
/**
* Token name
*/
name: string;
/**
* Token decimals
*/
decimals: number;
/**
* Token logo URL (optional)
*/
logoURI?: string;
/**
* Whether this is the native token
*/
isNative: boolean;
}
/**
* Bridge route between two chains
*/
export interface BridgeRoute {
/**
* Source chain ID
*/
fromChainId: number;
/**
* Destination chain ID (should be 138 for Chain 138)
*/
toChainId: number;
/**
* Supported tokens on this route
*/
tokens: BridgeableToken[];
/**
* Whether route is currently active
*/
active: boolean;
}
/**
* Bridge quote
*/
export interface BridgeQuote {
/**
* Source chain ID
*/
fromChainId: number;
/**
* Destination chain ID
*/
toChainId: number;
/**
* Token being bridged
*/
token: BridgeableToken;
/**
* Amount to bridge (in token units)
*/
amount: BigNumberish;
/**
* Estimated output amount (in token units)
*/
estimatedOutput: BigNumberish;
/**
* Bridge fee (in token units)
*/
fee: BigNumberish;
/**
* Estimated time in seconds
*/
estimatedTimeSeconds: number;
/**
* Minimum output amount (slippage protection)
*/
minimumOutput: BigNumberish;
}
/**
* Bridge transaction status
*/
export interface BridgeStatus {
/**
* Transaction hash on source chain
*/
sourceTxHash: string;
/**
* Transaction hash on destination chain (when available)
*/
destinationTxHash?: string;
/**
* Current status
*/
status: 'pending' | 'processing' | 'completed' | 'failed';
/**
* Current step description
*/
step: string;
/**
* Block number on source chain
*/
sourceBlockNumber?: number;
/**
* Block number on destination chain (when available)
*/
destinationBlockNumber?: number;
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../chain" }
]
}

19
packages/chain/README.md Normal file
View File

@@ -0,0 +1,19 @@
# @dbis-thirdweb/chain
Canonical Chain 138 definition for thirdweb integrations.
## Usage
```typescript
import { chain138 } from '@dbis-thirdweb/chain';
// Use with thirdweb SDK
const sdk = new ThirdwebSDK(chain138);
```
## Chain Details
- **ChainID**: 138
- **CAIP-2**: `eip155:138`
- **RPC**: `https://138.rpc.thirdweb.com`
- **Native Currency**: ETH (18 decimals)

View File

@@ -0,0 +1,36 @@
{
"name": "@dbis-thirdweb/chain",
"version": "0.1.0",
"description": "Chain 138 canonical definition for thirdweb",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"chain-138",
"blockchain",
"eip155:138"
],
"author": "",
"license": "MIT",
"dependencies": {
"@thirdweb-dev/chains": "^0.1.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,27 @@
import type { Chain } from '@thirdweb-dev/chains';
/**
* Chain 138 canonical definition
* CAIP-2: eip155:138
*/
export const chain138: Chain = {
chain: 'Chain 138',
chainId: 138,
rpc: ['https://138.rpc.thirdweb.com'],
nativeCurrency: {
name: 'Ether',
symbol: 'ETH',
decimals: 18,
},
shortName: 'chain138',
slug: 'chain-138',
testnet: false,
name: 'Chain 138',
// Explorer will be inferred from thirdweb or added if available
explorers: [],
};
/**
* Export as default for convenience
*/
export default chain138;

View File

@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": []
}

View File

@@ -0,0 +1,63 @@
# @dbis-thirdweb/http-api
HTTP API client wrapper for Chain 138 with retries, timeouts, and type-safe endpoints.
## Usage
### Basic Client
```typescript
import { createAPIClient } from '@dbis-thirdweb/http-api';
const client = createAPIClient({
apiKey: 'your-api-key',
timeout: 30000,
retries: 3,
});
// GET request
const data = await client.get('/endpoint');
// POST request
const result = await client.post('/endpoint', { data: 'value' });
```
### Chain 138 API Endpoints
```typescript
import { createChain138API } from '@dbis-thirdweb/http-api';
const api = createChain138API({
apiKey: 'your-api-key',
});
// Get chain metadata
const metadata = await api.getChainMetadata();
// Get transaction receipt
const receipt = await api.getTransactionReceipt('0x...');
// Get transaction logs
const logs = await api.getTransactionLogs('0x...');
// Get block
const block = await api.getBlock(12345);
// Get latest block
const latestBlock = await api.getLatestBlock();
// Send transaction (if supported)
const txResult = await api.sendTransaction({
to: '0x...',
value: '1000000000000000000',
});
```
## Features
- Standardized client wrapper with retry logic
- Exponential backoff for retries
- Configurable timeouts
- Chain 138 base URL configuration
- Type-safe request/response interfaces
- Automatic chain ID header injection

View File

@@ -0,0 +1,36 @@
{
"name": "@dbis-thirdweb/http-api",
"version": "0.1.0",
"description": "HTTP API client wrapper for Chain 138",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"http-api",
"chain-138",
"api-client"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,175 @@
import { chain138 } from '@dbis-thirdweb/chain';
/**
* HTTP API client configuration
*/
export interface APIClientConfig {
/**
* Base URL for API requests
*/
baseURL: string;
/**
* API key (if required)
*/
apiKey?: string;
/**
* Request timeout in milliseconds
* @default 30000
*/
timeout?: number;
/**
* Number of retries for failed requests
* @default 3
*/
retries?: number;
/**
* Retry delay in milliseconds (exponential backoff base)
* @default 1000
*/
retryDelay?: number;
/**
* Chain ID (should be Chain 138)
*/
chainId: number;
}
/**
* Default configuration for Chain 138
*/
export const defaultConfig: APIClientConfig = {
baseURL: 'https://api.thirdweb.com',
timeout: 30000,
retries: 3,
retryDelay: 1000,
chainId: chain138.chainId,
};
/**
* Sleep utility for retry delays
*/
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* HTTP API client with retry logic and timeout handling
*/
export class APIClient {
private config: APIClientConfig;
constructor(config: Partial<APIClientConfig> = {}) {
this.config = { ...defaultConfig, ...config };
}
/**
* Make HTTP request with retries and timeout
*/
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.config.baseURL}${endpoint}`;
const timeout = this.config.timeout || 30000;
// Add API key to headers if provided
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
};
if (this.config.apiKey) {
headers['Authorization'] = `Bearer ${this.config.apiKey}`;
}
// Add chain ID to headers if needed
headers['x-chain-id'] = this.config.chainId.toString();
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
let lastError: Error | null = null;
const maxRetries = this.config.retries || 3;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const response = await fetch(url, {
...options,
headers,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
return data as T;
} catch (error) {
clearTimeout(timeoutId);
lastError = error as Error;
// Don't retry on abort (timeout)
if (error instanceof Error && error.name === 'AbortError') {
throw new Error(`Request timeout after ${timeout}ms`);
}
// Don't retry on last attempt
if (attempt < maxRetries) {
const delay = this.config.retryDelay! * Math.pow(2, attempt); // Exponential backoff
await sleep(delay);
continue;
}
}
}
throw lastError || new Error('Request failed after retries');
}
/**
* GET request
*/
async get<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint, { method: 'GET' });
}
/**
* POST request
*/
async post<T>(endpoint: string, body?: unknown): Promise<T> {
return this.request<T>(endpoint, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined,
});
}
/**
* PUT request
*/
async put<T>(endpoint: string, body?: unknown): Promise<T> {
return this.request<T>(endpoint, {
method: 'PUT',
body: body ? JSON.stringify(body) : undefined,
});
}
/**
* DELETE request
*/
async delete<T>(endpoint: string): Promise<T> {
return this.request<T>(endpoint, { method: 'DELETE' });
}
}
/**
* Create API client for Chain 138
*/
export function createAPIClient(config?: Partial<APIClientConfig>): APIClient {
return new APIClient(config);
}

View File

@@ -0,0 +1,119 @@
import { APIClient, createAPIClient } from './client';
/**
* Chain metadata response
*/
export interface ChainMetadata {
chainId: number;
name: string;
nativeCurrency: {
name: string;
symbol: string;
decimals: number;
};
rpc: string[];
explorers?: Array<{
name: string;
url: string;
}>;
}
/**
* Transaction receipt response
*/
export interface TransactionReceipt {
blockHash: string;
blockNumber: number;
contractAddress: string | null;
cumulativeGasUsed: string;
effectiveGasPrice: string;
from: string;
gasUsed: string;
logs: Array<{
address: string;
topics: string[];
data: string;
}>;
logsBloom: string;
status: number;
to: string | null;
transactionHash: string;
transactionIndex: number;
}
/**
* Block response
*/
export interface BlockResponse {
number: number;
hash: string;
parentHash: string;
timestamp: number;
transactions: string[];
}
/**
* Chain 138 API endpoints wrapper
*/
export class Chain138API {
constructor(private client: APIClient) {}
/**
* Fetch chain metadata for Chain 138
*/
async getChainMetadata(): Promise<ChainMetadata> {
return this.client.get<ChainMetadata>('/v1/chains/138');
}
/**
* Get transaction receipt
*/
async getTransactionReceipt(txHash: string): Promise<TransactionReceipt> {
return this.client.get<TransactionReceipt>(`/v1/chains/138/transactions/${txHash}/receipt`);
}
/**
* Get transaction logs
*/
async getTransactionLogs(txHash: string): Promise<TransactionReceipt['logs']> {
const receipt = await this.getTransactionReceipt(txHash);
return receipt.logs;
}
/**
* Get block by number
*/
async getBlock(blockNumber: number): Promise<BlockResponse> {
return this.client.get<BlockResponse>(`/v1/chains/138/blocks/${blockNumber}`);
}
/**
* Get latest block
*/
async getLatestBlock(): Promise<BlockResponse> {
return this.client.get<BlockResponse>('/v1/chains/138/blocks/latest');
}
/**
* Send transaction via API (if supported)
*/
async sendTransaction(txData: {
to: string;
value?: string;
data?: string;
gasLimit?: string;
gasPrice?: string;
}): Promise<{ txHash: string }> {
return this.client.post<{ txHash: string }>('/v1/chains/138/transactions', txData);
}
}
/**
* Create Chain 138 API client
*/
export function createChain138API(
config?: Parameters<typeof createAPIClient>[0]
): Chain138API {
const client = createAPIClient(config);
return new Chain138API(client);
}

View File

@@ -0,0 +1,2 @@
export * from './client';
export * from './endpoints';

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../chain" }
]
}

85
packages/tokens/README.md Normal file
View File

@@ -0,0 +1,85 @@
# @dbis-thirdweb/tokens
ERC20/721/1155 token deployment and management for Chain 138.
## Usage
### Token Factory
```typescript
import { createTokenFactory } from '@dbis-thirdweb/tokens';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { chain138 } from '@dbis-thirdweb/chain';
const sdk = new ThirdwebSDK(chain138, privateKey);
const factory = createTokenFactory(sdk);
// Deploy ERC20
const erc20Address = await factory.deployERC20({
name: 'My Token',
symbol: 'MTK',
initialSupply: '1000000',
});
// Deploy ERC721
const erc721Address = await factory.deployERC721({
name: 'My NFT',
symbol: 'MNFT',
});
// Deploy ERC1155
const erc1155Address = await factory.deployERC1155({
name: 'My Edition',
});
```
### ERC20 Operations
```typescript
import { mintERC20, transferERC20, getERC20Balance } from '@dbis-thirdweb/tokens';
await mintERC20(sdk, erc20Address, '1000', recipientAddress);
await transferERC20(sdk, erc20Address, recipientAddress, '100');
const balance = await getERC20Balance(sdk, erc20Address, address);
```
### ERC721 Operations
```typescript
import { mintERC721, transferERC721, getERC721Metadata } from '@dbis-thirdweb/tokens';
const tokenId = await mintERC721(sdk, erc721Address, {
name: 'My NFT #1',
description: 'Description',
image: 'ipfs://...',
});
await transferERC721(sdk, erc721Address, tokenId, recipientAddress);
const metadata = await getERC721Metadata(sdk, erc721Address, tokenId);
```
### ERC1155 Operations
```typescript
import { mintERC1155, batchMintERC1155, getERC1155Balance } from '@dbis-thirdweb/tokens';
await mintERC1155(sdk, erc1155Address, 0n, '100', {
name: 'Edition #1',
image: 'ipfs://...',
});
await batchMintERC1155(sdk, erc1155Address, [
{ tokenId: 1n, amount: '50', metadata: {...} },
{ tokenId: 2n, amount: '25', metadata: {...} },
]);
const balance = await getERC1155Balance(sdk, erc1155Address, address, 0n);
```
## Features
- ERC20 deploy/mint/transfer/balance
- ERC721 deploy/mint/transfer/metadata
- ERC1155 deploy/batch mint/transfer
- Metadata hosting strategy (IPFS/thirdweb storage)
- Token factory utilities

View File

@@ -0,0 +1,40 @@
{
"name": "@dbis-thirdweb/tokens",
"version": "0.1.0",
"description": "ERC20/721/1155 token deployment and management for Chain 138",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"tokens",
"ERC20",
"ERC721",
"ERC1155",
"chain-138"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*",
"@thirdweb-dev/sdk": "^4.0.0",
"ethers": "^5.7.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,116 @@
import type { ThirdwebSDK } from '@thirdweb-dev/sdk';
import type { TokenMetadata } from './metadata';
import type { BigNumberish } from 'ethers';
/**
* ERC1155 token deployment parameters
*/
export interface ERC1155DeployParams {
name: string;
description?: string;
image?: string;
royaltyRecipient?: string;
royaltyBps?: number; // Basis points
}
/**
* Deploy ERC1155 token contract
*/
export async function deployERC1155(
sdk: ThirdwebSDK,
params: ERC1155DeployParams
): Promise<string> {
const contractAddress = await sdk.deployer.deployEdition({
name: params.name,
description: params.description,
image: params.image,
primary_sale_recipient: await sdk.getSigner()?.getAddress(),
});
return contractAddress;
}
/**
* Mint ERC1155 tokens (batch mint)
*/
export async function mintERC1155(
sdk: ThirdwebSDK,
contractAddress: string,
tokenId: bigint,
amount: BigNumberish,
metadata: TokenMetadata,
to?: string
): Promise<void> {
const contract = await sdk.getContract(contractAddress, 'edition');
const recipient = to || (await sdk.getSigner()?.getAddress()) || '';
await contract.erc1155.mintTo(recipient, {
metadata,
supply: amount.toString(),
});
}
/**
* Batch mint ERC1155 tokens
*/
export async function batchMintERC1155(
sdk: ThirdwebSDK,
contractAddress: string,
mints: Array<{
tokenId: bigint;
amount: BigNumberish;
metadata: TokenMetadata;
}>,
to?: string
): Promise<void> {
const contract = await sdk.getContract(contractAddress, 'edition');
const recipient = to || (await sdk.getSigner()?.getAddress()) || '';
const payloads = mints.map((mint) => ({
metadata: mint.metadata,
supply: mint.amount.toString(),
}));
await contract.erc1155.mintBatchTo(recipient, payloads);
}
/**
* Transfer ERC1155 tokens
*/
export async function transferERC1155(
sdk: ThirdwebSDK,
contractAddress: string,
tokenId: bigint,
amount: BigNumberish,
to: string
): Promise<void> {
const contract = await sdk.getContract(contractAddress, 'edition');
await contract.erc1155.transfer(to, tokenId.toString(), amount.toString());
}
/**
* Get ERC1155 token balance
*/
export async function getERC1155Balance(
sdk: ThirdwebSDK,
contractAddress: string,
address: string,
tokenId: bigint
): Promise<bigint> {
const contract = await sdk.getContract(contractAddress, 'edition');
const balance = await contract.erc1155.balanceOf(address, tokenId);
return BigInt(balance.toString());
}
/**
* Get ERC1155 token metadata
*/
export async function getERC1155Metadata(
sdk: ThirdwebSDK,
contractAddress: string,
tokenId: bigint
): Promise<TokenMetadata> {
const contract = await sdk.getContract(contractAddress, 'edition');
const nft = await contract.erc1155.get(tokenId.toString());
return nft.metadata as TokenMetadata;
}

View File

@@ -0,0 +1,81 @@
import type { Signer } from 'ethers';
import type { ThirdwebSDK } from '@thirdweb-dev/sdk';
import type { BigNumberish } from 'ethers';
/**
* ERC20 token deployment parameters
*/
export interface ERC20DeployParams {
name: string;
symbol: string;
description?: string;
image?: string;
initialSupply?: BigNumberish;
}
/**
* Deploy ERC20 token
*/
export async function deployERC20(
sdk: ThirdwebSDK,
params: ERC20DeployParams
): Promise<string> {
// Use thirdweb SDK to deploy ERC20
// This uses thirdweb's prebuilt ERC20 contract
const contractAddress = await sdk.deployer.deployToken({
name: params.name,
symbol: params.symbol,
description: params.description,
image: params.image,
primary_sale_recipient: await sdk.getSigner()?.getAddress(),
});
// If initial supply is specified, mint it
if (params.initialSupply) {
const contract = await sdk.getContract(contractAddress, 'token');
await contract.erc20.mint(params.initialSupply.toString());
}
return contractAddress;
}
/**
* Mint ERC20 tokens
*/
export async function mintERC20(
sdk: ThirdwebSDK,
contractAddress: string,
amount: BigNumberish,
to?: string
): Promise<void> {
const contract = await sdk.getContract(contractAddress, 'token');
const recipient = to || (await sdk.getSigner()?.getAddress()) || '';
await contract.erc20.mintTo(recipient, amount.toString());
}
/**
* Transfer ERC20 tokens
*/
export async function transferERC20(
sdk: ThirdwebSDK,
contractAddress: string,
to: string,
amount: BigNumberish
): Promise<void> {
const contract = await sdk.getContract(contractAddress, 'token');
await contract.erc20.transfer(to, amount.toString());
}
/**
* Get ERC20 balance
*/
export async function getERC20Balance(
sdk: ThirdwebSDK,
contractAddress: string,
address: string
): Promise<bigint> {
const contract = await sdk.getContract(contractAddress, 'token');
const balance = await contract.erc20.balanceOf(address);
return BigInt(balance.value.toString());
}

View File

@@ -0,0 +1,87 @@
import type { ThirdwebSDK } from '@thirdweb-dev/sdk';
import type { TokenMetadata } from './metadata';
/**
* ERC721 token deployment parameters
*/
export interface ERC721DeployParams {
name: string;
symbol: string;
description?: string;
image?: string;
royaltyRecipient?: string;
royaltyBps?: number; // Basis points (e.g., 500 = 5%)
}
/**
* Deploy ERC721 token contract
*/
export async function deployERC721(
sdk: ThirdwebSDK,
params: ERC721DeployParams
): Promise<string> {
const contractAddress = await sdk.deployer.deployNFTCollection({
name: params.name,
symbol: params.symbol,
description: params.description,
image: params.image,
primary_sale_recipient: await sdk.getSigner()?.getAddress(),
});
return contractAddress;
}
/**
* Mint ERC721 token
*/
export async function mintERC721(
sdk: ThirdwebSDK,
contractAddress: string,
metadata: TokenMetadata,
to?: string
): Promise<bigint> {
const contract = await sdk.getContract(contractAddress, 'nft-collection');
const recipient = to || (await sdk.getSigner()?.getAddress()) || '';
const result = await contract.mintTo(recipient, metadata);
return BigInt(result.id.toString());
}
/**
* Transfer ERC721 token
*/
export async function transferERC721(
sdk: ThirdwebSDK,
contractAddress: string,
tokenId: bigint,
to: string
): Promise<void> {
const contract = await sdk.getContract(contractAddress, 'nft-collection');
await contract.transfer(tokenId.toString(), to);
}
/**
* Get ERC721 token metadata
*/
export async function getERC721Metadata(
sdk: ThirdwebSDK,
contractAddress: string,
tokenId: bigint
): Promise<TokenMetadata> {
const contract = await sdk.getContract(contractAddress, 'nft-collection');
const nft = await contract.erc721.get(tokenId);
return nft.metadata as TokenMetadata;
}
/**
* Get ERC721 balance (number of tokens owned)
*/
export async function getERC721Balance(
sdk: ThirdwebSDK,
contractAddress: string,
address: string
): Promise<bigint> {
const contract = await sdk.getContract(contractAddress, 'nft-collection');
const balance = await contract.erc721.balanceOf(address);
return BigInt(balance.toString());
}

View File

@@ -0,0 +1,39 @@
import type { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { deployERC20, type ERC20DeployParams } from './erc20';
import { deployERC721, type ERC721DeployParams } from './erc721';
import { deployERC1155, type ERC1155DeployParams } from './erc1155';
/**
* Token factory for deploying different token types
*/
export class TokenFactory {
constructor(private sdk: ThirdwebSDK) {}
/**
* Deploy ERC20 token
*/
async deployERC20(params: ERC20DeployParams): Promise<string> {
return deployERC20(this.sdk, params);
}
/**
* Deploy ERC721 token
*/
async deployERC721(params: ERC721DeployParams): Promise<string> {
return deployERC721(this.sdk, params);
}
/**
* Deploy ERC1155 token
*/
async deployERC1155(params: ERC1155DeployParams): Promise<string> {
return deployERC1155(this.sdk, params);
}
}
/**
* Create token factory instance
*/
export function createTokenFactory(sdk: ThirdwebSDK): TokenFactory {
return new TokenFactory(sdk);
}

View File

@@ -0,0 +1,5 @@
export * from './metadata';
export * from './erc20';
export * from './erc721';
export * from './erc1155';
export * from './factory';

View File

@@ -0,0 +1,74 @@
/**
* Metadata hosting strategy configuration
*/
export interface MetadataConfig {
/**
* IPFS gateway URL for uploading/fetching metadata
*/
ipfsGateway: string;
/**
* Whether to pin metadata to IPFS
*/
pinToIpfs: boolean;
/**
* Alternative metadata storage options
*/
storage?: {
/**
* Use thirdweb storage
*/
useThirdwebStorage?: boolean;
/**
* Custom storage endpoint
*/
customEndpoint?: string;
};
}
/**
* Default metadata configuration
*/
export const defaultMetadataConfig: MetadataConfig = {
ipfsGateway: 'https://ipfs.io/ipfs/',
pinToIpfs: true,
storage: {
useThirdwebStorage: true,
},
};
/**
* Token metadata structure
*/
export interface TokenMetadata {
name: string;
description?: string;
image?: string;
external_url?: string;
attributes?: Array<{
trait_type: string;
value: string | number;
}>;
// Additional properties can be added as needed
[key: string]: unknown;
}
/**
* Generate token metadata URI
*/
export function generateMetadataURI(
metadata: TokenMetadata,
config: MetadataConfig = defaultMetadataConfig
): string {
// In production, this would upload to IPFS/thirdweb storage
// For now, return a placeholder
if (config.storage?.useThirdwebStorage) {
// Would use thirdweb storage SDK here
return 'ipfs://...';
}
// Fallback to direct IPFS gateway
return 'ipfs://...';
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../chain" }
]
}

View File

@@ -0,0 +1,46 @@
# @dbis-thirdweb/wallets
Wallet connectors and configuration for Chain 138.
## Usage
### Wallet Configuration
```typescript
import { getWalletConfig } from '@dbis-thirdweb/wallets';
const config = getWalletConfig({
confirmationBlocks: 2,
gasStrategy: {
multiplier: 1.5,
},
});
```
### Chain Switching
```typescript
import { switchToChain138, ensureChain138 } from '@dbis-thirdweb/wallets';
import { useWallet } from '@thirdweb-dev/react';
function MyComponent() {
const wallet = useWallet();
const handleSwitch = async () => {
await switchToChain138(wallet);
};
// Or use ensure (switches only if not already on Chain 138)
const handleEnsure = async () => {
await ensureChain138(wallet);
};
}
```
## Features
- Chain 138 wallet configuration defaults
- Gas strategy configuration
- RPC failover support
- Chain switching utilities
- Automatic chain addition if missing

View File

@@ -0,0 +1,40 @@
{
"name": "@dbis-thirdweb/wallets",
"version": "0.1.0",
"description": "Wallet connectors and configuration for Chain 138",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"wallet",
"chain-138",
"connector"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*",
"@thirdweb-dev/chains": "^0.1.0",
"@thirdweb-dev/react": "^4.0.0",
"@thirdweb-dev/sdk": "^4.0.0",
"ethers": "^5.7.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,71 @@
import { chain138 } from '@dbis-thirdweb/chain';
import type { Chain } from '@thirdweb-dev/chains';
/**
* Wallet interface for chain switching operations
* Compatible with thirdweb wallet connectors
*/
export interface WalletLike {
getChainId(): number;
switchChain(chainId: number): Promise<void>;
addChain(chain: Chain): Promise<void>;
}
/**
* Switch wallet to Chain 138
* @param wallet - Connected wallet instance
* @returns Promise that resolves when chain switch is complete
*/
export async function switchToChain138(wallet: WalletLike): Promise<void> {
try {
// Check if wallet is already on Chain 138
if (wallet.getChainId() === chain138.chainId) {
return;
}
// Attempt to switch chain
await wallet.switchChain(chain138.chainId);
} catch (error: unknown) {
// If chain doesn't exist in wallet, try to add it
if (
error &&
typeof error === 'object' &&
'code' in error &&
(error.code === 4902 || error.code === -32603)
) {
await addChain138(wallet);
await wallet.switchChain(chain138.chainId);
} else {
throw error;
}
}
}
/**
* Add Chain 138 to wallet if it doesn't exist
* @param wallet - Connected wallet instance
*/
export async function addChain138(wallet: WalletLike): Promise<void> {
const chain: Chain = chain138;
await wallet.addChain(chain);
}
/**
* Check if wallet is connected to Chain 138
* @param wallet - Connected wallet instance
* @returns true if wallet is on Chain 138
*/
export function isOnChain138(wallet: WalletLike): boolean {
return wallet.getChainId() === chain138.chainId;
}
/**
* Ensure wallet is on Chain 138, switching if necessary
* @param wallet - Connected wallet instance
* @returns Promise that resolves when wallet is on Chain 138
*/
export async function ensureChain138(wallet: WalletLike): Promise<void> {
if (!isOnChain138(wallet)) {
await switchToChain138(wallet);
}
}

View File

@@ -0,0 +1,3 @@
export * from './walletConfig';
export * from './chainSwitching';
export { chain138 } from '@dbis-thirdweb/chain';

View File

@@ -0,0 +1,92 @@
import { chain138 } from '@dbis-thirdweb/chain';
/**
* Wallet configuration defaults for Chain 138
*/
export interface WalletConfig {
/**
* Number of confirmation blocks required before considering a transaction final
* @default 1
*/
confirmationBlocks: number;
/**
* Gas strategy configuration
*/
gasStrategy: {
/**
* Gas price multiplier (1.0 = current gas price, 1.2 = 20% more)
* @default 1.2
*/
multiplier: number;
/**
* Maximum gas price in gwei (0 = no limit)
* @default 0
*/
maxGasPriceGwei: number;
};
/**
* RPC failover configuration
*/
rpcFailover: {
/**
* Primary RPC endpoint
*/
primary: string;
/**
* Fallback RPC endpoints (used if primary fails)
*/
fallbacks: string[];
/**
* Timeout in milliseconds for RPC requests
* @default 10000
*/
timeout: number;
/**
* Number of retries before switching to fallback
* @default 2
*/
retries: number;
};
}
/**
* Default wallet configuration for Chain 138
*/
export const defaultWalletConfig: WalletConfig = {
confirmationBlocks: 1,
gasStrategy: {
multiplier: 1.2,
maxGasPriceGwei: 0,
},
rpcFailover: {
primary: chain138.rpc[0] || 'https://138.rpc.thirdweb.com',
fallbacks: [],
timeout: 10000,
retries: 2,
},
};
/**
* Get wallet configuration for Chain 138 with optional overrides
*/
export function getWalletConfig(overrides?: Partial<WalletConfig>): WalletConfig {
return {
...defaultWalletConfig,
...overrides,
gasStrategy: {
...defaultWalletConfig.gasStrategy,
...overrides?.gasStrategy,
},
rpcFailover: {
...defaultWalletConfig.rpcFailover,
...overrides?.rpcFailover,
fallbacks: overrides?.rpcFailover?.fallbacks ?? defaultWalletConfig.rpcFailover.fallbacks,
},
};
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../chain" }
]
}

43
packages/x402/README.md Normal file
View File

@@ -0,0 +1,43 @@
# @dbis-thirdweb/x402
x402 payment primitives and pay-to-access flows for Chain 138.
## Usage
### Pay-to-Access Flow
```typescript
import { PayToAccessFlow, InMemoryReplayProtectionStore } from '@dbis-thirdweb/x402';
import { ThirdwebSDK } from '@thirdweb-dev/sdk';
import { chain138 } from '@dbis-thirdweb/chain';
// Server-side: Create request
const sdk = new ThirdwebSDK(chain138);
const provider = sdk.getProvider();
const replayStore = new InMemoryReplayProtectionStore();
const flow = new PayToAccessFlow(provider, replayStore);
const request = await flow.createRequest({
amount: ethers.utils.parseEther('0.01'),
recipient: '0x...',
expiresInSeconds: 3600,
});
const challenge = flow.generateChallenge(request);
// Client-side: Fulfill payment
const signer = await wallet.getSigner();
const receipt = await flow.fulfillPayment(challenge, signer);
// Server-side: Verify payment
const isValid = await flow.verifyPayment(receipt, request);
```
## Features
- Payment request creation and validation
- Replay protection via request ID tracking
- Receipt verification on-chain
- Pay-to-access flow implementation
- Expiration handling

View File

@@ -0,0 +1,38 @@
{
"name": "@dbis-thirdweb/x402",
"version": "0.1.0",
"description": "x402 payment primitives and pay-to-access flows for Chain 138",
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts",
"lint": "eslint src",
"test": "echo \"No tests yet\""
},
"keywords": [
"thirdweb",
"x402",
"payment",
"chain-138"
],
"author": "",
"license": "MIT",
"dependencies": {
"@dbis-thirdweb/chain": "workspace:*",
"@thirdweb-dev/sdk": "^4.0.0",
"ethers": "^5.7.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"tsup": "^8.0.0",
"typescript": "^5.0.0"
}
}

View File

@@ -0,0 +1,118 @@
import type { Signer, providers } from 'ethers';
import { ethers } from 'ethers';
import type { PaymentRequest, PaymentChallenge, PaymentReceipt } from '../types';
import { validateRequest, type ReplayProtectionStore } from '../replayProtection';
import { verifyReceipt, waitForReceipt } from '../receiptVerification';
import { chain138 } from '@dbis-thirdweb/chain';
/**
* Generate payment challenge for x402 pay-to-access flow
*/
export function generateChallenge(request: PaymentRequest): PaymentChallenge {
const nonce = ethers.utils.hexlify(ethers.utils.randomBytes(32));
// Create message to sign (includes request details + nonce for replay protection)
const message = JSON.stringify({
requestId: request.requestId,
amount: request.amount.toString(),
recipient: request.recipient,
expiresAt: request.expiresAt,
nonce,
chainId: chain138.chainId,
});
return {
request,
nonce,
message,
};
}
/**
* x402 Pay-to-Access Flow
* HTTP request → x402 challenge → settlement on-chain
*/
export class PayToAccessFlow {
constructor(
private provider: providers.Provider,
private replayStore: ReplayProtectionStore
) {}
/**
* Create a payment request (server-side)
*/
async createRequest(params: {
amount: bigint;
recipient: string;
expiresInSeconds?: number;
metadata?: string;
}): Promise<PaymentRequest> {
const requestId = ethers.utils.hexlify(ethers.utils.randomBytes(32));
const expiresAt = Math.floor(Date.now() / 1000) + (params.expiresInSeconds || 3600);
const request: PaymentRequest = {
requestId,
amount: params.amount,
recipient: params.recipient,
expiresAt,
metadata: params.metadata,
};
// Validate request (check for expiration and replay)
await validateRequest(request, this.replayStore);
return request;
}
/**
* Generate challenge from request (server-side)
*/
generateChallenge(request: PaymentRequest): PaymentChallenge {
return generateChallenge(request);
}
/**
* Fulfill payment (client-side with signer)
*/
async fulfillPayment(
challenge: PaymentChallenge,
signer: Signer
): Promise<PaymentReceipt> {
// Validate request before processing
await validateRequest(challenge.request, this.replayStore);
// Create and send transaction
const tx = await signer.sendTransaction({
to: challenge.request.recipient,
value: challenge.request.amount,
});
// Wait for confirmation
const receipt = await waitForReceipt(tx.hash, this.provider);
// Mark request as used to prevent replay
await this.replayStore.markAsUsed(challenge.request.requestId);
return {
...receipt,
requestId: challenge.request.requestId,
};
}
/**
* Verify payment receipt (server-side)
*/
async verifyPayment(
receipt: PaymentReceipt,
originalRequest: PaymentRequest
): Promise<boolean> {
const isValid = await verifyReceipt(receipt, originalRequest, this.provider);
if (isValid) {
// Mark as used if verification succeeds
await this.replayStore.markAsUsed(originalRequest.requestId);
}
return isValid;
}
}

View File

@@ -0,0 +1,4 @@
export * from './types';
export * from './replayProtection';
export * from './receiptVerification';
export * from './flows/payToAccess';

View File

@@ -0,0 +1,72 @@
import type { providers } from 'ethers';
import type { PaymentReceipt, PaymentRequest } from './types';
import { chain138 } from '@dbis-thirdweb/chain';
/**
* Verify payment receipt on-chain
*/
export async function verifyReceipt(
receipt: PaymentReceipt,
request: PaymentRequest,
provider: providers.Provider
): Promise<boolean> {
try {
// Get transaction receipt
const txReceipt = await provider.getTransactionReceipt(receipt.txHash);
if (!txReceipt) {
return false;
}
// Verify transaction is on correct chain
// Note: Provider should already be configured for Chain 138
// Verify transaction succeeded
if (txReceipt.status !== 1) {
return false;
}
// Verify block number matches
if (txReceipt.blockNumber !== receipt.blockNumber) {
return false;
}
// Verify amount was transferred (check logs or transaction value)
// This is a simplified check - in production, you'd verify specific logs/events
const tx = await provider.getTransaction(receipt.txHash);
if (tx && tx.value.toString() !== request.amount.toString()) {
return false;
}
return true;
} catch (error) {
console.error('Error verifying receipt:', error);
return false;
}
}
/**
* Wait for payment receipt confirmation
*/
export async function waitForReceipt(
txHash: string,
provider: providers.Provider,
confirmations: number = 1
): Promise<PaymentReceipt> {
const receipt = await provider.waitForTransaction(txHash, confirmations);
if (!receipt) {
throw new Error(`Transaction ${txHash} not found`);
}
if (receipt.status !== 1) {
throw new Error(`Transaction ${txHash} failed`);
}
return {
txHash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
requestId: '', // Should be extracted from transaction data/logs
confirmedAt: Math.floor(Date.now() / 1000),
};
}

View File

@@ -0,0 +1,50 @@
import type { PaymentRequest } from './types';
/**
* Storage interface for replay protection
*/
export interface ReplayProtectionStore {
hasBeenUsed(requestId: string): Promise<boolean>;
markAsUsed(requestId: string): Promise<void>;
}
/**
* In-memory replay protection store (for testing/single-instance use)
*/
export class InMemoryReplayProtectionStore implements ReplayProtectionStore {
private usedRequests = new Set<string>();
async hasBeenUsed(requestId: string): Promise<boolean> {
return this.usedRequests.has(requestId);
}
async markAsUsed(requestId: string): Promise<void> {
this.usedRequests.add(requestId);
}
}
/**
* Check if payment request has expired
*/
export function isRequestExpired(request: PaymentRequest): boolean {
const now = Math.floor(Date.now() / 1000);
return request.expiresAt < now;
}
/**
* Validate payment request for replay protection
*/
export async function validateRequest(
request: PaymentRequest,
store: ReplayProtectionStore
): Promise<void> {
// Check expiration
if (isRequestExpired(request)) {
throw new Error(`Payment request ${request.requestId} has expired`);
}
// Check if already used
if (await store.hasBeenUsed(request.requestId)) {
throw new Error(`Payment request ${request.requestId} has already been used`);
}
}

View File

@@ -0,0 +1,76 @@
import type { BigNumberish } from 'ethers';
/**
* x402 Payment Request
*/
export interface PaymentRequest {
/**
* Unique request ID (prevents replay attacks)
*/
requestId: string;
/**
* Payment amount in native currency (wei)
*/
amount: BigNumberish;
/**
* Recipient address
*/
recipient: string;
/**
* Timestamp when request expires (Unix timestamp in seconds)
*/
expiresAt: number;
/**
* Optional metadata or description
*/
metadata?: string;
}
/**
* x402 Payment Challenge
*/
export interface PaymentChallenge {
/**
* Original request
*/
request: PaymentRequest;
/**
* Nonce for replay protection
*/
nonce: string;
/**
* Challenge message to sign
*/
message: string;
}
/**
* x402 Payment Receipt
*/
export interface PaymentReceipt {
/**
* Transaction hash
*/
txHash: string;
/**
* Block number where transaction was confirmed
*/
blockNumber: number;
/**
* Request ID that was fulfilled
*/
requestId: string;
/**
* Timestamp of confirmation
*/
confirmedAt: number;
}

View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"composite": false
},
"include": ["src/**/*"],
"references": [
{ "path": "../chain" }
]
}