Wrapping dShares™

Learn how to wrap dShares into ERC-20 tokens on HyperEVM and transfer them to HyperCore.

What is wrapping?

Wrapping is the process of converting a native asset into a standardized token format (ERC-20), enabling native assets to interact with smart contracts across multiple chains. Understanding this concept is fundamental to cross-chain asset management.

In the Hyperliquid ecosystem, wrapping allows dShares™ to transition from the native HyperCore trading ledger into standard ERC-20 tokens on HyperEVM. This process works by locking the native dShares asset and minting a wrapped equivalent in return, making the shares compatible with EVM smart contracts and enabling interaction with the broader DeFi ecosystem.

dShares™ cannot be bridged directly to other chains. To move them, you must first wrap them into wrapped dShares and then send the wrapped tokens to a HyperCore bridge.

sequenceDiagram
    actor P as Partner
    participant API as Dinari API
    participant HE as HyperEVM
    participant L1 as Dinari L1
    participant HC as HyperCore
    Note over P,HC: Phase 1 — Purchase the dShare
    P->>API: 1. Request order permit
    API-->>P: Return permit to sign
    P->>API: 2. Submit signed permit
    API->>HE: 3. Submit order on HyperEVM
    HE->>L1: Bridge order to Dinari L1
    L1->>L1: 4. Process order
    L1->>HE: Bridge dShare back to HyperEVM
    Note over P,HC: Phase 2 — Move to HyperCore
    P->>HE: 5. deposit() on wrapped dShare
    HE-->>P: 6. Receive wrapped dShare
    P->>HE: 7. transfer() to bridge address
    HE->>HC: Tokens arrive on HyperCore

Integration guide to wrapping dShares™

To bridge your dShares™, complete three on-chain interactions:

  1. Approval: Grant the wrapped dShare contract permission to move your dShares. This security step explicitly authorizes how many tokens the contract can spend on your behalf.
  2. Deposit (wrap): Call the deposit function to exchange your native dShares for their wrapped equivalent. This locks your original assets and mints the corresponding wrapped tokens to your account.
  3. Bridge: Transfer your newly created wrapped dShares to the HyperCore bridge address, which initiates the cross-chain transfer to your destination.

Addresses for SPCX dShare and wrapped dShare

AddressValue
Native dShare contract0x9b4Db2271EB1fa0aEe1abA7ed55E51C38cE514a3
Wrapped dShare contract0x95687557C66BC799a850bA7037673528238ae763
HyperEVM to HyperCore bridge address0x2000000000000000000000000000000000000262
HyperCore token index610

Step 1: Approval

Initialize the chain manager and account

Start by initializing the EVM manager for your chain.

import Dinari from '@dinari/api-sdk';
import { ethers } from 'ethers'; // v6

// Dinari API SDK is used to linked your wallet to your account
const client = new Dinari({
  apiKeyID: process.env['DINARI_API_KEY_ID'],
  apiSecretKey: process.env['DINARI_API_SECRET_KEY'],
});

// HyperEVM mainnet is chain ID 999. Use the testnet RPC only if testing on 998.
const provider = new ethers.JsonRpcProvider('https://rpc.hyperliquid.xyz/evm');
const wallet = new ethers.Wallet(process.env['WALLET_PRIVATE_KEY']!, provider);

// Set these to the dShare being wrapped and its wrapped dShare on the target chain.
const dshareAddress = 'dShare-Address';
const wrappedDshareAddress = 'your-wrapped-dShare-address';

Load the native and wrapped dShare contracts

// Generic ERC20 Interfaces
const ERC20_ABI = [
  'function allowance(address owner, address spender) view returns (uint256)',
  'function approve(address spender, uint256 amount) returns (bool)',
  'function balanceOf(address owner) view returns (uint256)',
  'function decimals() view returns (uint8)',
  'function transfer(address to, uint256 amount) returns (bool)',
];

// Create the Wrapped ABI for the Existing Contract
const WRAPPED_ABI = [...ERC20_ABI, 'function deposit(uint256 assets, address receiver) returns (uint256 shares)'];

const dshare = new ethers.Contract(dshareAddress, ERC20_ABI, wallet);
const wrappedDshare = new ethers.Contract(wrappedDshareAddress, WRAPPED_ABI, wallet);

Link your wallet to your Account

// Link your wallet to your Dinari account -this is required to use the API.
async function main() {
  const accountID = 'your-account-id';
  const nonceResp = await client.v2.accounts.wallet.external.getNonce(accountID, {
    wallet_address: wallet.address,
  });

  // Sign the nonce message to prove ownership
  const signature = await wallet.signMessage(nonceResp.message);

  // Connect you wallet to your Dinari Account using the signed nonce
  await client.v2.accounts.wallet.external.connect(accountID, {
    chain_id: 'eip155:999',
    nonce: nonceResp.nonce,
    signature,
    wallet_address: wallet.address,
  });

Set the total amount of shares to wrap and contract decimals

  // Set the total amount of shares to wrap and contract decimals
  const decimals = await dshare.decimals();
  const totalAmount = ethers.parseUnits('10', decimals);

Now you can sign your approval

  const currentAllowance = await dshare.allowance(wallet.address, wrappedDshareAddress);
  if (currentAllowance < totalAmount) {
    const approveTx = await dshare.approve(wrappedDshareAddress, totalAmount);
    await approveTx.wait();
  }

Step 2: Deposit (wrap)

Specify the amount of native dShares you intend to wrap and deposit them into the wrapped dShare contract. The contract mints wrapped dShares to your account in return.

// Wrapping spends native dShares, so validate the native dShare balance.
const balance = await dshare.balanceOf(wallet.address);
	if (balance < totalAmount) {
  	throw new Error(`Insufficient dShare balance: have ${balance}, need ${totalAmount}`);
}
  
// Your deposit needs the total amount of shares to wrap and the receiver address on the target chain.
const wrappedBefore = await wrappedDshare.balanceOf(wallet.address);
const depositTx = await wrappedDshare.deposit(totalAmount, wallet.address);
await depositTx.wait();
const wrappedAfter = await wrappedDshare.balanceOf(wallet.address);
const mintedAmount = wrappedAfter - wrappedBefore;

Step 3: Transfer to the HyperCore bridge

Transfer the wrapped tokens to the HyperCore bridge address. This initiates the cross-chain transfer. Bridge the amount that was actually minted in Step 2 (minted_amount), send it to the correct bridge address, and verify the transaction on-chain so the bridge can process and finalize the transfer.

const bridgeAddress = 'target-bridge-address';
const transferTx = await wrappedDshare.transfer(bridgeAddress, mintedAmount);
await transferTx.wait();

Complete reference script

This is the complete, uninterrupted script for bridging a dShare to HyperCore:

import Dinari from '@dinari/api-sdk';
import { ethers } from 'ethers'; // v6

// Dinari API SDK (REST): used here to link the wallet to your account.
const client = new Dinari({
  apiKeyID: process.env['DINARI_API_KEY_ID'],
  apiSecretKey: process.env['DINARI_API_SECRET_KEY'],
});

// HyperEVM mainnet is chain ID 999. Use the testnet RPC only if testing on 998.
const provider = new ethers.JsonRpcProvider('https://rpc.hyperliquid.xyz/evm');
const wallet = new ethers.Wallet(process.env['WALLET_PRIVATE_KEY']!, provider);

// Set these to the dShare being wrapped and its wrapped dShare on the target chain.
const dshareAddress = 'dShare-Address';
const wrappedDshareAddress = 'your-wrapped-dShare-address';

// Minimal ERC-20 (dShare) and ERC-4626 (wrapped dShare) interfaces.
// See tje ERC-20 spec: https://eips.ethereum.org/EIPS/eip-20
// See the ERC-4626 spec: https://eips.ethereum.org/EIPS/eip-4626
const ERC20_ABI = [
  'function allowance(address owner, address spender) view returns (uint256)',
  'function approve(address spender, uint256 amount) returns (bool)',
  'function balanceOf(address owner) view returns (uint256)',
  'function decimals() view returns (uint8)',
  'function transfer(address to, uint256 amount) returns (bool)',
];
const WRAPPED_ABI = [...ERC20_ABI, 'function deposit(uint256 assets, address receiver) returns (uint256 shares)'];


const dshare = new ethers.Contract(dshareAddress, ERC20_ABI, wallet);
const wrappedDshare = new ethers.Contract(wrappedDshareAddress, WRAPPED_ABI, wallet);

// Link your wallet to your Dinari account -this is required to use the API.
async function main() {
  const accountID = 'your-account-id';
  const nonceResp = await client.v2.accounts.wallet.external.getNonce(accountID, {
    wallet_address: wallet.address,
  });

  // Sign the nonce message to prove ownership
  const signature = await wallet.signMessage(nonceResp.message);

  // Connect you wallet to your Dinari Account using the signed nonce
  await client.v2.accounts.wallet.external.connect(accountID, {
    chain_id: 'eip155:999',
    nonce: nonceResp.nonce,
    signature,
    wallet_address: wallet.address,
  });

  // Set the total amount of shares to wrap and contract decimals
  const decimals = await dshare.decimals();
  const totalAmount = ethers.parseUnits('10', decimals);

  // --- Step 1: Approval ---
  const currentAllowance = await dshare.allowance(wallet.address, wrappedDshareAddress);
  if (currentAllowance < totalAmount) {
    const approveTx = await dshare.approve(wrappedDshareAddress, totalAmount);
    await approveTx.wait();
  }

  // --- Step 2: Deposit (wrap) ---
  // Wrapping spends native dShares, so validate the native dShare balance.
  const balance = await dshare.balanceOf(wallet.address);
  if (balance < totalAmount) {
    throw new Error(`Insufficient dShare balance: have ${balance}, need ${totalAmount}`);
  }

  // Your deposit needs the total amount of shares to wrap and the receiver address on the target chain.
  const wrappedBefore = await wrappedDshare.balanceOf(wallet.address);
  const depositTx = await wrappedDshare.deposit(totalAmount, wallet.address);
  await depositTx.wait();
  const wrappedAfter = await wrappedDshare.balanceOf(wallet.address);
  const mintedAmount = wrappedAfter - wrappedBefore;

  // --- Step 3: Transfer to the HyperCore bridge ---
  const bridgeAddress = 'target-bridge-address';
  const transferTx = await wrappedDshare.transfer(bridgeAddress, mintedAmount);
  await transferTx.wait();
}

Wrapping Using Hyper Explorer

One alternative to programmatic wrapping, is using Hyper Explorer to wrap a dShare by utilizing Proxy Contract Interactions on HyperEVM with the token you intend like to wrap. The same high level steps are performed, however they are performed as contract interactions on the hyperevm scan page. In this example, we're using SpaceX (SPCX)

Step 1: Provide Approval to your Connected Wallet

Navigate to the contract page on HyperEVM Scan for the SPCX Token and utilize the approve() function call.

For example SPCX (SpaceX Wrapped Token). Navigate to its token listing on HyperEVM Scan, navigate to the Contract Page, and select Write As Proxy. Go to the Approve Function and set the spender to the wrapped token contract address and the amount you want to wrap.

For SpaceX the wrapped token contract address is: 0x95687557C66BC799a850bA7037673528238ae763

HyperEVM Scan approval form for approving the SPCX wrapped token contract as spender

Step 2: Perform the Deposit

Call the deposit function on the same page with the assets set to the number of dShares you want to wrap and the to address. Direct Link for SpaceX Deposit Function

HyperEVM Scan deposit function for wrapping SPCX dShares

Step 3: Transfer the Tokens

Call the transfer function for the token with the transfer address set to the assets HyperEVM Bridge Address, for SPCX this is 0x2000000000000000000000000000000000000262, and amount set to the amount of wrapped dShares retrieved from previous call. Direct Link for SpaceX Transfer Function

Note: This address changes depending on the token. Be sure you provide the specific address for the dShare you're wrapping.

HyperEVM Scan transfer function for sending wrapped SPCX dShares to the HyperEVM bridge address