Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes and refactoring for bridge clients #20

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ yarn add omni-bridge-sdk
The SDK currently provides a split interface for cross-chain transfers:

- `omniTransfer`: A unified interface for initiating transfers from any supported chain
- Chain-specific deployers: Required for finalizing transfers on destination chains
- Chain-specific clients: Required for finalizing transfers on destination chains

> [!NOTE]
> We're working on unifying this into a single interface that will handle the complete transfer lifecycle. For now, you'll need to use both `omniTransfer` and chain-specific deployers as shown below.
> We're working on unifying this into a single interface that will handle the complete transfer lifecycle. For now, you'll need to use both `omniTransfer` and chain-specific clients as shown below.

Here's a complete example:

Expand All @@ -53,7 +53,7 @@ import {
ChainKind,
omniAddress,
OmniBridgeAPI,
getDeployer,
getClient,
} from "omni-bridge-sdk";
import { connect } from "near-api-js";

Expand Down Expand Up @@ -97,8 +97,8 @@ do {

if (status === "ready_for_finalize") {
// 4. Finalize transfer on destination chain
const ethDeployer = getDeployer(ChainKind.Eth, ethWallet);
await ethDeployer.finalizeTransfer(transferMessage, signature);
const ethClient = getClient(ChainKind.Eth, ethWallet);
await ethClient.finalizeTransfer(transferMessage, signature);
break;
}

Expand Down Expand Up @@ -131,16 +131,16 @@ const status = await api.getTransferStatus(chain, nonce);

### 3. Transfer Finalization

When status is "ready_for_finalize", use chain-specific deployers to complete the transfer:
When status is "ready_for_finalize", use chain-specific clients to complete the transfer:

```typescript
// Finalize on Ethereum/EVM chains
const evmDeployer = getDeployer(ChainKind.Eth, ethWallet);
await evmDeployer.finalizeTransfer(transferMessage, signature);
const evmClient = getClient(ChainKind.Eth, ethWallet);
await evmClient.finalizeTransfer(transferMessage, signature);

// Finalize on NEAR
const nearDeployer = getDeployer(ChainKind.Near, nearAccount);
await nearDeployer.finalizeTransfer(
const nearClient = getClient(ChainKind.Near, nearAccount);
await nearClient.finalizeTransfer(
token,
recipientAccount,
storageDeposit,
Expand All @@ -149,8 +149,8 @@ await nearDeployer.finalizeTransfer(
);

// Finalize on Solana
const solDeployer = getDeployer(ChainKind.Sol, provider);
await solDeployer.finalizeTransfer(transferMessage, signature);
const solClient = getClient(ChainKind.Sol, provider);
await solClient.finalizeTransfer(transferMessage, signature);
```

## Core Concepts
Expand Down Expand Up @@ -245,20 +245,20 @@ const result = await omniTransfer(provider, transfer);

### Deploying Tokens

Token deployment uses chain-specific deployers through a unified interface:
Token deployment uses chain-specific clients through a unified interface:

```typescript
import { getDeployer } from "omni-bridge-sdk";
import { getClient } from "omni-bridge-sdk";

// Initialize deployer for source chain
const deployer = getDeployer(ChainKind.Near, wallet);
// Initialize client for source chain
const client = getClient(ChainKind.Near, wallet);

// Example: Deploy NEAR token to Ethereum
const txHash = await deployer.logMetadata("near:token.near");
const txHash = await client.logMetadata("near:token.near");
console.log(`Metadata logged with tx: ${txHash}`);

// Deploy token with signed MPC payload
const result = await deployer.deployToken(signature, {
const result = await client.deployToken(signature, {
token: "token.near",
name: "Token Name",
symbol: "TKN",
Expand Down
48 changes: 24 additions & 24 deletions docs/token-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,30 @@ Important: To deploy a token on any chain, it must first exist on NEAR. You cann
### NEAR Chain Deployment

```typescript
import { NearDeployer, ChainKind } from "omni-bridge-sdk";
import { NearBridgeClient, ChainKind } from "omni-bridge-sdk";
import { connect } from "near-api-js";

// Setup NEAR connection
const near = await connect({
networkId: "testnet",
nodeUrl: "https://rpc.testnet.near.org",
});
const account = await near.account("deployer.near");
const account = await near.account("client.near");

// Initialize deployer
const deployer = new NearDeployer(account);
// Initialize client
const client = new NearBridgeClient(account);

// 1. Log metadata for existing NEAR token
const logTxHash = await deployer.logMetadata("near:token.near");
const logTxHash = await client.logMetadata("near:token.near");

// 2. Deploy to destination chain (e.g., Ethereum)
const deployTxHash = await deployer.deployToken(
const deployTxHash = await client.deployToken(
ChainKind.Eth,
vaa // Wormhole VAA containing deployment approval
);

// 3. For tokens being deployed TO NEAR, bind them after deployment
await deployer.bindToken(
await client.bindToken(
ChainKind.Eth, // Source chain
vaa, // Optional: Wormhole VAA
evmProof // Optional: EVM proof (for EVM chains)
Expand All @@ -50,21 +50,21 @@ await deployer.bindToken(
### EVM Chain Deployment (Ethereum/Base/Arbitrum)

```typescript
import { EVMDeployer, ChainKind } from "omni-bridge-sdk";
import { EvmBridgeClient, ChainKind } from "omni-bridge-sdk";
import { ethers } from "ethers";

// Setup EVM wallet
const provider = new ethers.providers.Web3Provider(window.ethereum);
const wallet = provider.getSigner();

// Initialize deployer for specific chain
const deployer = new EVMDeployer(wallet, ChainKind.Eth);
// Initialize client for specific chain
const client = new EvmBridgeClient(wallet, ChainKind.Eth);

// 1. Log metadata for existing token
const logTxHash = await deployer.logMetadata("eth:0x123...");
const logTxHash = await client.logMetadata("eth:0x123...");

// 2. Deploy token using MPC signature
const { txHash, tokenAddress } = await deployer.deployToken(
const { txHash, tokenAddress } = await client.deployToken(
signature, // MPC signature authorizing deployment
{
token: "token_id",
Expand All @@ -78,27 +78,27 @@ const { txHash, tokenAddress } = await deployer.deployToken(
### Solana Deployment

```typescript
import { SolanaDeployer } from "omni-bridge-sdk";
import { SolanaBridgeClient } from "omni-bridge-sdk";
import { Connection, PublicKey, Keypair } from "@solana/web3.js";

// Setup Solana connection
const connection = new Connection("https://api.testnet.solana.com");
const payer = Keypair.generate();

// Initialize deployer
const deployer = new SolanaDeployer(
// Initialize client
const client = new SolanaBridgeClient(
provider,
new PublicKey("wormhole_program_id")
);

// 1. Log metadata for existing SPL token
const logTxHash = await deployer.logMetadata(
const logTxHash = await client.logMetadata(
tokenPubkey,
payer // Optional payer for transaction
);

// 2. Deploy token using MPC signature
const { txHash, tokenAddress } = await deployer.deployToken(
const { txHash, tokenAddress } = await client.deployToken(
signature,
{
token: "token_id",
Expand All @@ -116,7 +116,7 @@ Each deployment step can encounter different types of errors that need handling:

```typescript
try {
await deployer.logMetadata("near:token.near");
await client.logMetadata("near:token.near");
} catch (error) {
if (error.message.includes("Token metadata not provided")) {
// Handle missing metadata
Expand Down Expand Up @@ -164,12 +164,12 @@ console.log(status); // "pending" | "ready_for_finalize" | "finalized" | "ready_

```typescript
// For EVM chains
const evmDeployer = new EVMDeployer(wallet, ChainKind.Eth);
const nearTokenAddress = await evmDeployer.factory.nearToEthToken("token.near");
const evmClient = new EvmClient(wallet, ChainKind.Eth);
const nearTokenAddress = await evmClient.factory.nearToEthToken("token.near");

// For Solana
const solDeployer = new SolanaDeployer(provider, wormholeProgramId);
const isBridgedToken = await solDeployer.isBridgedToken(tokenPubkey);
const solClient = new SolanaClient(provider, wormholeProgramId);
const isBridgedToken = await solClient.isBridgedToken(tokenPubkey);
```

## Security Considerations
Expand All @@ -193,7 +193,7 @@ const isBridgedToken = await solDeployer.isBridgedToken(tokenPubkey);

```typescript
// Check required balances on NEAR
const { regBalance, initBalance, storage } = await deployer.getBalances();
const { regBalance, initBalance, storage } = await client.getBalances();
const requiredBalance = regBalance + initBalance;
```

Expand Down Expand Up @@ -222,7 +222,7 @@ if (!signature.isValidFor(ChainKind.Eth)) {
while ((await api.getDeploymentStatus(txHash)).status !== "ready_for_bind") {
await new Promise((r) => setTimeout(r, 1000));
}
await deployer.bindToken(sourceChain, vaa);
await client.bindToken(sourceChain, vaa);
```

## Chain Support Matrix
Expand Down
30 changes: 9 additions & 21 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,39 @@ import { AnchorProvider as SolWallet } from "@coral-xyz/anchor"
import { PublicKey } from "@solana/web3.js"
import { Wallet as EthWallet } from "ethers"
import { Account as NearAccount } from "near-api-js"
import { EVMDeployer } from "./deployer/evm"
import { NearDeployer } from "./deployer/near"
import { SolanaDeployer } from "./deployer/solana"
import { EvmBridgeClient } from "./clients/evm"
import { NearBridgeClient } from "./clients/near"
import { SolanaBridgeClient } from "./clients/solana"
import { ChainKind, type OmniTransferMessage, type OmniTransferResult } from "./types"

export async function omniTransfer(
wallet: EthWallet | NearAccount | SolWallet,
transfer: OmniTransferMessage,
): Promise<OmniTransferResult> {
if (wallet instanceof NearAccount) {
const deployer = new NearDeployer(wallet, "omni-locker.testnet") // TODO: Get from config
const { nonce, hash } = await deployer.initTransfer(
transfer.tokenAddress,
transfer.recipient,
transfer.amount,
)
const client = new NearBridgeClient(wallet)
const { nonce, hash } = await client.initTransfer(transfer)
return {
txId: hash,
nonce: BigInt(nonce),
}
}

if (wallet instanceof EthWallet) {
const deployer = new EVMDeployer(wallet, ChainKind.Eth)
const { hash, nonce } = await deployer.initTransfer(
transfer.tokenAddress,
transfer.recipient,
transfer.amount,
)
const client = new EvmBridgeClient(wallet, ChainKind.Eth)
const { hash, nonce } = await client.initTransfer(transfer)
return {
txId: hash,
nonce: BigInt(nonce),
}
}

if (wallet instanceof SolWallet) {
const deployer = new SolanaDeployer(
const client = new SolanaBridgeClient(
wallet,
new PublicKey("3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5"),
) // TODO: Get from config
const { nonce, hash } = await deployer.initTransfer(
transfer.tokenAddress,
transfer.recipient,
transfer.amount,
)
const { nonce, hash } = await client.initTransfer(transfer)
return {
txId: hash,
nonce: BigInt(nonce),
Expand Down
32 changes: 14 additions & 18 deletions src/deployer/evm.ts → src/clients/evm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import type {
ChainKind,
MPCSignature,
OmniAddress,
OmniTransferMessage,
TokenMetadata,
TransferMessagePayload,
U128,
} from "../types"
import { getChain } from "../utils"

Expand Down Expand Up @@ -68,15 +68,15 @@ const FACTORY_ADDRESSES: Record<ChainTag<EVMChainKind>, string | undefined> = {
}

/**
* EVM blockchain implementation of the token deployer
* EVM blockchain implementation of the bridge client
*/
export class EVMDeployer {
export class EvmBridgeClient {
private factory: ethers.Contract
private chainKind: EVMChainKind
private chainTag: ChainTag<EVMChainKind>

/**
* Creates a new EVM token deployer instance
* Creates a new EVM bridge client instance
* @param wallet - Ethereum signer instance for transaction signing
* @param chain - The EVM chain to deploy to (Ethereum, Base, or Arbitrum)
* @throws {Error} If factory address is not configured for the chain or if chain is not EVM
Expand Down Expand Up @@ -109,7 +109,7 @@ export class EVMDeployer {
async logMetadata(tokenAddress: OmniAddress): Promise<string> {
const sourceChain = getChain(tokenAddress)

// Validate source chain matches the deployer's chain
// Validate source chain matches the client's chain
if (!ChainUtils.areEqual(sourceChain, this.chainKind)) {
throw new Error(`Token address must be on ${this.chainTag}`)
}
Expand Down Expand Up @@ -168,27 +168,23 @@ export class EVMDeployer {
* @throws {Error} If token address is not on the correct EVM chain
* @returns Promise resolving to object containing transaction hash and nonce
*/
async initTransfer(
token: OmniAddress,
recipient: OmniAddress,
amount: U128,
): Promise<{ hash: string; nonce: number }> {
const sourceChain = getChain(token)

// Validate source chain matches the deployer's chain
async initTransfer(transfer: OmniTransferMessage): Promise<{ hash: string; nonce: number }> {
const sourceChain = getChain(transfer.tokenAddress)

// Validate source chain matches the client's chain
if (!ChainUtils.areEqual(sourceChain, this.chainKind)) {
throw new Error(`Token address must be on ${this.chainTag}`)
}

const [_, tokenAccountId] = token.split(":")
const [_, tokenAccountId] = transfer.tokenAddress.split(":")

try {
const tx = await this.factory.initTransfer(
tokenAccountId,
amount.valueOf(),
0,
0,
recipient,
transfer.amount,
transfer.fee,
transfer.nativeFee,
transfer.recipient,
"",
)
return {
Expand Down
Loading
Loading