Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/1inch/cross-chain-sdk/llms.txt

Use this file to discover all available pages before exploring further.

What are Secrets?

Secrets are random 32-byte values that serve as the cryptographic key to unlock funds in atomic cross-chain swaps. They are the core security primitive that ensures only authorized parties can claim tokens from escrow contracts.
A secret acts like a password that locks your funds. The hash of the secret is public (the lock), but the secret itself remains private until you choose to reveal it.

Why Secrets are Needed

Secrets provide the security guarantees that make atomic swaps trustless:
  1. Atomic Execution: Secrets ensure both sides of the swap complete or neither does
  2. Privacy: Hash locks reveal no information about the secret
  3. Authorization: Only the user with the secret can authorize the final execution
  4. Non-repudiation: Once revealed, the secret proves the swap can proceed

The Secret Lifecycle

Generating Secrets Securely

Secure random generation is critical—predictable secrets compromise the entire swap.

Using Node.js Crypto

The recommended approach uses Node.js’s crypto.randomBytes():
import { randomBytes } from 'crypto'

// Generate a cryptographically secure random secret
const secret = '0x' + randomBytes(32).toString('hex')

console.log('Secret:', secret)
// Example output: 0x8f3b9d2e1a4c5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d0c1b2a3f4e5d6c7b8a9f0e
Never use predictable values for secrets:
  • ❌ Simple patterns: '0x' + '1'.repeat(64)
  • ❌ Timestamps: Date.now().toString()
  • ❌ Sequential numbers: counter++
  • ✅ Always use: randomBytes(32)

Secret Requirements

Secrets must meet strict requirements:
// From the SDK validation
function isValidSecret(secret: string): boolean {
  return (
    secret.startsWith('0x') &&
    secret.length === 66 &&        // 0x + 64 hex chars
    /^0x[0-9a-f]{64}$/i.test(secret) // Valid hex
  )
}
Requirements:
  • Format: Hex string prefixed with 0x
  • Length: Exactly 32 bytes (64 hex characters)
  • Randomness: Cryptographically secure random generation
  • Uniqueness: Each secret must be unique

Secret Hashing with Keccak256

Before using secrets in hash locks, they must be hashed:
import { HashLock } from '@1inch/cross-chain-sdk'

const secret = '0x' + randomBytes(32).toString('hex')
const secretHash = HashLock.hashSecret(secret)

console.log('Secret:', secret)
console.log('Hash:', secretHash)

Hash Function Implementation

// From src/domains/hash-lock/hash-lock.ts:17-24
import { keccak256 } from 'ethers'

public static hashSecret(secret: string): string {
    assert(
        isHexBytes(secret) && getBytesCount(secret) === 32n,
        'secret length must be 32 bytes hex encoded'
    )

    return keccak256(secret)
}
Keccak256 is the same hash function used throughout Ethereum. It’s a one-way function: given a hash, it’s computationally infeasible to find the original secret without trying all possibilities.

Multiple Secrets for Multiple Fills

Some swaps require multiple secrets to support partial fills:
import { HashLock } from '@1inch/cross-chain-sdk'
import { randomBytes } from 'crypto'

// Get quote to determine how many secrets needed
const quote = await sdk.getQuote({
  amount: '10000000',
  srcChainId: NetworkEnum.POLYGON,
  dstChainId: NetworkEnum.BINANCE,
  srcTokenAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
  dstTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
  walletAddress
})

const preset = quote.presets.fast

// Generate the required number of secrets
const secrets = Array.from({
  length: preset.secretsCount
}).map(() => '0x' + randomBytes(32).toString('hex'))

console.log(`Generated ${secrets.length} secrets`)

Why Multiple Secrets?

  • Partial Fills: Different resolvers can fill different parts of the order
  • Optimization: Allows better price execution through competition
  • Flexibility: Larger orders can be split across multiple resolvers
Each secret is bound to an index in the merkle tree:
// From src/domains/hash-lock/hash-lock.ts:32-42
import { solidityPackedKeccak256 } from 'ethers'

public static getMerkleLeavesFromSecretHashes(
    secretHashes: string[]
): MerkleLeaf[] {
    return secretHashes.map(
        (s, idx) =>
            solidityPackedKeccak256(
                ['uint64', 'bytes32'],
                [idx, s]
            ) as MerkleLeaf
    )
}
This ensures each fill has a unique commitment even if secrets were accidentally duplicated.

When and How to Submit Secrets

Timing is critical—submit secrets too early and resolvers can front-run; too late and the swap may time out.

The Correct Flow

  1. Create Order: Submit order with hash lock (secret hash, not secret)
  2. Wait for Escrows: Resolver deploys source and destination escrows
  3. Verify Escrows: Check that escrows are correctly deployed
  4. Submit Secrets: Reveal secrets to the 1inch relayer
  5. Swap Executes: Resolver claims source tokens, user receives destination tokens
import { OrderStatus } from '@1inch/cross-chain-sdk'

// After creating and submitting order...

while (true) {
  // Check which fills are ready for secrets
  const secretsToShare = await sdk.getReadyToAcceptSecretFills(orderHash)

  if (secretsToShare.fills.length) {
    for (const { idx } of secretsToShare.fills) {
      // Reveal the secret for this fill
      await sdk.submitSecret(orderHash, secrets[idx])
      console.log(`Submitted secret ${idx}`)
    }
  }

  // Check if swap is complete
  const { status } = await sdk.getOrderStatus(orderHash)

  if (
    status === OrderStatus.Executed ||
    status === OrderStatus.Expired ||
    status === OrderStatus.Refunded
  ) {
    break
  }

  await sleep(1000)
}

Understanding getReadyToAcceptSecretFills()

This method returns fills where:
  • Both source and destination escrows are deployed
  • Escrow addresses match expected values
  • The fill hasn’t already been completed
interface SecretFill {
  idx: number          // Index of the secret to submit
  srcEscrow: string   // Deployed source escrow address
  dstEscrow: string   // Deployed destination escrow address
}

interface ReadyToAcceptSecretFills {
  fills: SecretFill[]
}

Security Considerations

Secret Storage

Store secrets securely during the swap process:
  • ✅ Keep in memory only during swap execution
  • ✅ Clear secrets from memory after use
  • ✅ Never log secrets to console in production
  • ❌ Don’t store in files unless encrypted
  • ❌ Don’t commit secrets to version control
  • ❌ Don’t transmit over unencrypted channels

Verification Before Revelation

Always verify escrow deployments before submitting secrets:
const secretsToShare = await sdk.getReadyToAcceptSecretFills(orderHash)

for (const { idx, srcEscrow, dstEscrow } of secretsToShare.fills) {
  // Additional verification (recommended for production)
  
  // 1. Verify escrow addresses match expected contracts
  const expectedSrcEscrow = calculateExpectedEscrowAddress(/* params */)
  if (srcEscrow !== expectedSrcEscrow) {
    console.error('Source escrow address mismatch!')
    continue
  }
  
  // 2. Verify escrow has correct amount
  const escrowBalance = await getEscrowBalance(dstEscrow)
  if (escrowBalance < expectedAmount) {
    console.error('Insufficient escrow balance!')
    continue
  }
  
  // 3. Submit secret only after verification
  await sdk.submitSecret(orderHash, secrets[idx])
}
The SDK’s getReadyToAcceptSecretFills() performs basic checks, but production applications should implement additional verification logic specific to their security requirements.

Secret Lifecycle Best Practices

import { randomBytes } from 'crypto'

class SecretManager {
  private secrets: Map<number, string> = new Map()
  
  // Generate and store secrets
  generateSecrets(count: number): string[] {
    const secrets = Array.from({ length: count }).map(
      (_, idx) => {
        const secret = '0x' + randomBytes(32).toString('hex')
        this.secrets.set(idx, secret)
        return secret
      }
    )
    return secrets
  }
  
  // Retrieve secret by index
  getSecret(idx: number): string | undefined {
    return this.secrets.get(idx)
  }
  
  // Clear secrets after use
  clear(): void {
    this.secrets.clear()
  }
}

// Usage
const manager = new SecretManager()
const secrets = manager.generateSecrets(preset.secretsCount)

try {
  // Use secrets for swap...
  await performSwap(secrets)
} finally {
  // Always clear secrets
  manager.clear()
}

Complete Example

Here’s a complete example showing proper secret management:
import {
  SDK,
  HashLock,
  NetworkEnum,
  PresetEnum,
  OrderStatus
} from '@1inch/cross-chain-sdk'
import { randomBytes } from 'crypto'

const sdk = new SDK({ /* config */ })

async function performCrossChainSwap() {
  // 1. Get quote
  const quote = await sdk.getQuote({
    amount: '10000000',
    srcChainId: NetworkEnum.POLYGON,
    dstChainId: NetworkEnum.BINANCE,
    srcTokenAddress: '0xc2132d05d31c914a87c6611c10748aeb04b58e8f',
    dstTokenAddress: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
    walletAddress
  })

  const preset = PresetEnum.fast

  // 2. Generate secrets securely
  const secrets = Array.from({
    length: quote.presets[preset].secretsCount
  }).map(() => '0x' + randomBytes(32).toString('hex'))

  // 3. Create hash lock
  const hashLock = secrets.length === 1
    ? HashLock.forSingleFill(secrets[0])
    : HashLock.forMultipleFills(HashLock.getMerkleLeaves(secrets))

  const secretHashes = secrets.map(s => HashLock.hashSecret(s))

  // 4. Create and submit order
  const { hash, quoteId, order } = await sdk.createOrder(quote, {
    walletAddress,
    hashLock,
    preset,
    secretHashes
  })

  await sdk.submitOrder(
    quote.srcChainId,
    order,
    quoteId,
    secretHashes
  )

  console.log('Order submitted:', hash)

  // 5. Submit secrets when ready
  while (true) {
    const secretsToShare = await sdk.getReadyToAcceptSecretFills(hash)

    if (secretsToShare.fills.length) {
      for (const { idx } of secretsToShare.fills) {
        await sdk.submitSecret(hash, secrets[idx])
        console.log(`Submitted secret ${idx}`)
      }
    }

    const { status } = await sdk.getOrderStatus(hash)

    if (
      status === OrderStatus.Executed ||
      status === OrderStatus.Expired ||
      status === OrderStatus.Refunded
    ) {
      break
    }

    await new Promise(resolve => setTimeout(resolve, 1000))
  }

  const finalStatus = await sdk.getOrderStatus(hash)
  console.log('Swap complete:', finalStatus)
}
  • Hash Locks: How secrets are used to create hash locks
  • Atomic Swaps: The role of secrets in atomic execution
  • Presets: How presets determine the number of secrets needed