importhashlibimportjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportHash256,PrivateKeyfromsymbolchain.facade.SymbolFacadeimportSymbolFacadefromsymbolchain.symbol.IdGeneratorimportgenerate_mosaic_alias_idfromsymbolchain.symbol.NetworkimportNetworkTimestampfromsymbolchain.scimportAmountfromweb3importWeb3SYMBOL_NODE_URL=os.getenv('SYMBOL_NODE_URL','https://reference.symboltest.net:3001')print(f'Using Symbol node {SYMBOL_NODE_URL}')ETH_RPC_URL=os.getenv('ETH_RPC_URL','https://ethereum-sepolia-rpc.publicnode.com')print(f'Using Ethereum RPC {ETH_RPC_URL}')# Ethereum HTLC contract on SepoliaHTLC_ADDRESS='0xd58e030bd21c7788897aE5Ea845DaBA936e91D2B'HTLC_ABI=[{'name':'newContract','type':'function','stateMutability':'payable','inputs':[{'name':'_receiver','type':'address'},{'name':'_hashlock','type':'bytes32'},{'name':'_timelock','type':'uint256'}],'outputs':[{'name':'contractId','type':'bytes32'}]},{'name':'withdraw','type':'function','stateMutability':'nonpayable','inputs':[{'name':'_contractId','type':'bytes32'},{'name':'_preimage','type':'bytes'}],'outputs':[{'name':'','type':'bool'}]},{'name':'getContract','type':'function','stateMutability':'view','inputs':[{'name':'_contractId','type':'bytes32'}],'outputs':[{'name':'sender','type':'address'},{'name':'receiver','type':'address'},{'name':'amount','type':'uint256'},{'name':'hashlock','type':'bytes32'},{'name':'timelock','type':'uint256'},{'name':'withdrawn','type':'bool'},{'name':'refunded','type':'bool'},{'name':'preimage','type':'bytes'}]},{'name':'LogHTLCNew','type':'event','inputs':[{'name':'contractId','type':'bytes32','indexed':True},{'name':'sender','type':'address','indexed':True},{'name':'receiver','type':'address','indexed':True},{'name':'amount','type':'uint256','indexed':False},{'name':'hashlock','type':'bytes32','indexed':False},{'name':'timelock','type':'uint256','indexed':False}]}]# Helper function to fetch current Symbol network timedefget_network_time():time_path='/node/time'print(f'Fetching current network time from {time_path}')withurllib.request.urlopen(f'{SYMBOL_NODE_URL}{time_path}')asresponse:response_json=json.loads(response.read().decode())timestamp=NetworkTimestamp(int(response_json['communicationTimestamps']['receiveTimestamp']))print(f' Network time: {timestamp.timestamp}'' ms since nemesis')returntimestamp# Helper function to fetch recommended Symbol fee multiplierdefget_fee_multiplier():fee_path='/network/fees/transaction'print(f'Fetching recommended fees from {fee_path}')withurllib.request.urlopen(f'{SYMBOL_NODE_URL}{fee_path}')asresponse:response_json=json.loads(response.read().decode())median_mult=response_json['medianFeeMultiplier']minimum_mult=response_json['minFeeMultiplier']fee_mult=max(median_mult,minimum_mult)print(f' Fee multiplier: {fee_mult}')returnfee_mult# Helper function to announce a Symbol transactiondefannounce_transaction(payload,endpoint,label):print(f'Announcing {label} to {endpoint}')request=urllib.request.Request(f'{SYMBOL_NODE_URL}{endpoint}',data=payload.encode(),headers={'Content-Type':'application/json'},method='PUT')withurllib.request.urlopen(request)asresponse:print(f' Response: {response.read().decode()}')# Helper function to wait for Symbol transaction statusdefwait_for_status(hash_value,expected_status,label):print(f'Waiting for {label} to reach {expected_status} status...')attempts=0max_attempts=60whileattempts<max_attempts:try:url=(f'{SYMBOL_NODE_URL}/transactionStatus/{hash_value}')withurllib.request.urlopen(url)asresponse:status=json.loads(response.read().decode())print(f' Transaction status: {status["group"]}')ifstatus['group']=='failed':raiseException(f'{label} failed: {status["code"]}')ifstatus['group']==expected_status:print(f'{label}{expected_status}'f' in {attempts} seconds')returnexcepturllib.error.HTTPErrorase:ife.code!=404:raiseprint(' Transaction status: not yet available')attempts+=1time.sleep(1)raiseException(f'{label} not {expected_status} after {max_attempts} attempts')# Poll Symbol for a confirmed secret proof transaction matching# a hashlock.defwait_for_secret_proof(signer_address,hashlock):hashlock_hex=hashlock.hex().upper()url=(f'{SYMBOL_NODE_URL}/transactions/confirmed'f'?address={signer_address}&type=16978&order=desc')print(f'Polling {url}')print(f' Looking for secret: {hashlock_hex}')attempts=0max_attempts=60whileattempts<max_attempts:withurllib.request.urlopen(url)asresponse:data=json.loads(response.read().decode())fortxindata.get('data',[]):secret=tx['transaction'].get('secret','')ifsecret.upper()==hashlock_hex:print(f' Found proof transaction after {attempts}s')returnbytes.fromhex(tx['transaction']['proof'])attempts+=1time.sleep(1)raiseException(f'Secret proof not found after {max_attempts} attempts')# Symbol accountsfacade=SymbolFacade('testnet')ALICE_XYM_PRIVATE_KEY=os.getenv('ALICE_XYM_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000000')alice_xym_key_pair=SymbolFacade.KeyPair(PrivateKey(ALICE_XYM_PRIVATE_KEY))alice_xym_address=facade.network.public_key_to_address(alice_xym_key_pair.public_key)print(f'Alice Symbol address: {alice_xym_address}')BOB_XYM_PRIVATE_KEY=os.getenv('BOB_XYM_PRIVATE_KEY','1111111111111111111111111111111111111111111111111111111111111111')bob_xym_key_pair=SymbolFacade.KeyPair(PrivateKey(BOB_XYM_PRIVATE_KEY))bob_xym_address=facade.network.public_key_to_address(bob_xym_key_pair.public_key)print(f'Bob Symbol address: {bob_xym_address}')# Ethereum accountsw3=Web3(Web3.HTTPProvider(ETH_RPC_URL))ALICE_ETH_PRIVATE_KEY=os.getenv('ALICE_ETH_PRIVATE_KEY','0xa73276699ba72dc7b5c9d08deaf8cd88eec33c866341b120304432b89586d45d')alice_eth_account=w3.eth.account.from_key(ALICE_ETH_PRIVATE_KEY)print(f'Alice ETH address: {alice_eth_account.address}')BOB_ETH_PRIVATE_KEY=os.getenv('BOB_ETH_PRIVATE_KEY','0x8e85561005f27d926af79a7ce3e76e75108a09ff2ab78bf65b5578d2e5d509bf')bob_eth_account=w3.eth.account.from_key(BOB_ETH_PRIVATE_KEY)print(f'Bob ETH address: {bob_eth_account.address}')try:# --- Alice: Generate proof and hashlock ---print('\n--- Alice: Generate proof and hashlock ---')proof=os.urandom(32)print(f'Proof (hex): {proof.hex()}')first_hash=hashlib.sha256(proof).digest()secret=hashlib.sha256(first_hash).digest()print(f'Secret (double SHA-256): {secret.hex()}')# --- Step 1. Alice: Lock ETH on Ethereum ---print('\n--- Step 1. Alice: Lock ETH on Ethereum ---')htlc=w3.eth.contract(address=HTLC_ADDRESS,abi=HTLC_ABI)timelock=int(time.time())+72*60*60print(f'Ethereum timelock (Unix): {timelock}')lock_call=htlc.functions.newContract(bob_eth_account.address,secret,timelock)lock_tx=lock_call.build_transaction({'from':alice_eth_account.address,'value':w3.to_wei(0.01,'ether'),'nonce':w3.eth.get_transaction_count(alice_eth_account.address)})signed_lock_tx=alice_eth_account.sign_transaction(lock_tx)lock_tx_hash=w3.eth.send_raw_transaction(signed_lock_tx.raw_transaction)print(f'Lock TX hash: {lock_tx_hash.hex()}')lock_receipt=w3.eth.wait_for_transaction_receipt(lock_tx_hash)print(f'Lock confirmed in block {lock_receipt.blockNumber}')# Extract the contractId from the LogHTLCNew eventcontract_id=lock_receipt.logs[0].topics[1]print(f'HTLC contract ID: {contract_id.hex()}')# --- Step 2. Bob: Create secret lock on Symbol ---print('\n--- Step 2. Bob: Create secret lock on Symbol ---')# Bob queries the Ethereum contract to get the hashlockcontract_info=htlc.functions.getContract(contract_id).call()hashlock=contract_info[3]# hashlock fieldprint(f'Hashlock from chain: {hashlock.hex()}')lock_duration=5760# ~48h at 30s blocksprint(f'Lock duration: {lock_duration} blocks')secret_lock_transaction=facade.transaction_factory.create({'type':'secret_lock_transaction_v1','signer_public_key':bob_xym_key_pair.public_key,'deadline':get_network_time().add_hours(2).timestamp,'recipient_address':alice_xym_address,'mosaic':{'mosaic_id':generate_mosaic_alias_id('symbol.xym'),'amount':1_000000# 1 XYM},'duration':lock_duration,'secret':Hash256(hashlock),'hash_algorithm':'hash_256'})secret_lock_transaction.fee=Amount(get_fee_multiplier()*secret_lock_transaction.size)# Sign and announcelock_signature=facade.sign_transaction(bob_xym_key_pair,secret_lock_transaction)lock_payload=facade.transaction_factory.attach_signature(secret_lock_transaction,lock_signature)print('Built secret lock transaction:')print(json.dumps(secret_lock_transaction.to_json(),indent=2))lock_hash=facade.hash_transaction(secret_lock_transaction)print(f'Secret lock transaction hash: {lock_hash}')announce_transaction(lock_payload,'/transactions','secret lock')wait_for_status(lock_hash,'confirmed','Secret lock')# --- Step 3. Alice: Claim XYM on Symbol ---print('\n--- Step 3. Alice: Claim XYM on Symbol ---')secret_proof_transaction=facade.transaction_factory.create({'type':'secret_proof_transaction_v1','signer_public_key':alice_xym_key_pair.public_key,'deadline':get_network_time().add_hours(2).timestamp,'recipient_address':alice_xym_address,'secret':Hash256(hashlock),'hash_algorithm':'hash_256','proof':proof})secret_proof_transaction.fee=Amount(get_fee_multiplier()*secret_proof_transaction.size)# Sign and announceproof_signature=facade.sign_transaction(alice_xym_key_pair,secret_proof_transaction)proof_payload=facade.transaction_factory.attach_signature(secret_proof_transaction,proof_signature)print('Built secret proof transaction:')print(json.dumps(secret_proof_transaction.to_json(),indent=2))proof_hash=facade.hash_transaction(secret_proof_transaction)print(f'Secret proof transaction hash: {proof_hash}')announce_transaction(proof_payload,'/transactions','secret proof')wait_for_status(proof_hash,'confirmed','Secret proof')# --- Step 4. Bob: Withdraw ETH on Ethereum ---print('\n--- Step 4. Bob: Withdraw ETH on Ethereum ---')# Bob waits for Alice to reveal the proof on Symbol.revealed_proof=wait_for_secret_proof(alice_xym_address,hashlock)print(f'Proof from chain: {revealed_proof.hex()}')withdraw_call=htlc.functions.withdraw(contract_id,revealed_proof)withdraw_tx=withdraw_call.build_transaction({'from':bob_eth_account.address,'nonce':w3.eth.get_transaction_count(bob_eth_account.address)})signed_withdraw_tx=bob_eth_account.sign_transaction(withdraw_tx)withdraw_tx_hash=w3.eth.send_raw_transaction(signed_withdraw_tx.raw_transaction)print(f'Withdraw TX hash: {withdraw_tx_hash.hex()}')withdraw_receipt=w3.eth.wait_for_transaction_receipt(withdraw_tx_hash)print(f'Withdraw confirmed in block {withdraw_receipt.blockNumber}')print('\n--- Cross-chain swap complete ---')excepturllib.error.URLErrorase:print(e.reason)exceptExceptionase:print(e)
import{PrivateKey}from'symbol-sdk';import{SymbolFacade,NetworkTimestamp,models,generateMosaicAliasId}from'symbol-sdk/symbol';import{createHash,randomBytes}from'crypto';import{ethers}from'ethers';constSYMBOL_NODE_URL=process.env.SYMBOL_NODE_URL||'https://reference.symboltest.net:3001';console.log('Using Symbol node',SYMBOL_NODE_URL);constETH_RPC_URL=process.env.ETH_RPC_URL||'https://ethereum-sepolia-rpc.publicnode.com';console.log('Using Ethereum RPC',ETH_RPC_URL);// Ethereum HTLC contract on SepoliaconstHTLC_ADDRESS='0xd58e030bd21c7788897aE5Ea845DaBA936e91D2B';constHTLC_ABI=['function newContract(address, bytes32, uint) '+'external payable returns (bytes32)','function withdraw(bytes32, bytes) '+'external returns (bool)','function getContract(bytes32) external view '+'returns (address sender, address receiver, '+'uint amount, bytes32 hashlock, '+'uint timelock, bool withdrawn, '+'bool refunded, bytes preimage)','event LogHTLCNew(bytes32 indexed contractId, '+'address indexed sender, '+'address indexed receiver, uint amount, '+'bytes32 hashlock, uint timelock)'];// Helper function to fetch current Symbol network timeasyncfunctiongetNetworkTime(){consttimePath='/node/time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${SYMBOL_NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();consttimestamp=newNetworkTimestamp(timeJSON.communicationTimestamps.receiveTimestamp);console.log(' Network time:',timestamp.timestamp,'ms since nemesis');returntimestamp;}// Helper function to fetch recommended Symbol fee multiplierasyncfunctiongetFeeMultiplier(){constfeePath='/network/fees/transaction';console.log('Fetching recommended fees from',feePath);constfeeResponse=awaitfetch(`${SYMBOL_NODE_URL}${feePath}`);constfeeJSON=awaitfeeResponse.json();constmedianMult=feeJSON.medianFeeMultiplier;constminimumMult=feeJSON.minFeeMultiplier;constfeeMult=Math.max(medianMult,minimumMult);console.log(' Fee multiplier:',feeMult);returnfeeMult;}// Helper function to announce a Symbol transactionasyncfunctionannounceTransaction(payload,endpoint,label){console.log(`Announcing ${label} to ${endpoint}`);constresponse=awaitfetch(`${SYMBOL_NODE_URL}${endpoint}`,{method:'PUT',headers:{'Content-Type':'application/json'},body:payload});console.log(' Response:',awaitresponse.text());}// Helper function to wait for Symbol transaction statusasyncfunctionwaitForStatus(hash,expectedStatus,label){console.log(`Waiting for ${label} to reach ${expectedStatus} status...`);letattempts=0;constmaxAttempts=60;while(attempts<maxAttempts){try{consturl=`${SYMBOL_NODE_URL}/transactionStatus/${hash}`;constresponse=awaitfetch(url);if(!response.ok){consterror=newError(`HTTP ${response.status}: ${response.statusText}`);error.status=response.status;throwerror;}conststatus=awaitresponse.json();console.log(' Transaction status:',status.group);if(status.group==='failed'){thrownewError(`${label} failed: ${status.code}`);}if(status.group===expectedStatus){console.log(`${label}${expectedStatus} in ${attempts} seconds`);return;}}catch(error){if(error.status===404){console.log(' Transaction status: not yet available');}else{throwerror;}}attempts++;awaitnewPromise(resolve=>setTimeout(resolve,1000));}thrownewError(`${label} not ${expectedStatus} after ${maxAttempts} attempts`);}// Poll Symbol for a confirmed secret proof transaction matching// a hashlock.asyncfunctionwaitForSecretProof(signerAddress,hashlock){consthashlockHex=hashlock.toUpperCase();consturl=`${SYMBOL_NODE_URL}/transactions/confirmed`+`?address=${signerAddress}&type=16978&order=desc`;console.log(`Polling ${url}`);console.log(` Looking for secret: ${hashlockHex}`);letattempts=0;constmaxAttempts=60;while(attempts<maxAttempts){constresponse=awaitfetch(url);constdata=awaitresponse.json();for(consttxofdata.data||[]){constsecret=(tx.transaction.secret||'').toUpperCase();if(secret===hashlockHex){console.log(` Found proof transaction after ${attempts}s`);returnBuffer.from(tx.transaction.proof,'hex');}}attempts++;awaitnewPromise(resolve=>setTimeout(resolve,1000));}thrownewError(`Secret proof not found after ${maxAttempts} attempts`);}// Symbol accountsconstfacade=newSymbolFacade('testnet');// Alice (creates the ETH lock, claims XYM on Symbol)constALICE_XYM_PRIVATE_KEY=process.env.ALICE_XYM_PRIVATE_KEY||'0000000000000000000000000000000000000000000000000000000000000000';constaliceXymKeyPair=newSymbolFacade.KeyPair(newPrivateKey(ALICE_XYM_PRIVATE_KEY));constaliceXymAddress=facade.network.publicKeyToAddress(aliceXymKeyPair.publicKey);console.log('Alice Symbol address:',aliceXymAddress.toString());// Bob (creates the XYM lock, claims ETH on Ethereum)constBOB_XYM_PRIVATE_KEY=process.env.BOB_XYM_PRIVATE_KEY||'1111111111111111111111111111111111111111111111111111111111111111';constbobXymKeyPair=newSymbolFacade.KeyPair(newPrivateKey(BOB_XYM_PRIVATE_KEY));constbobXymAddress=facade.network.publicKeyToAddress(bobXymKeyPair.publicKey);console.log('Bob Symbol address:',bobXymAddress.toString());// Ethereum accountsconstethProvider=newethers.JsonRpcProvider(ETH_RPC_URL);constALICE_ETH_PRIVATE_KEY=process.env.ALICE_ETH_PRIVATE_KEY||'0xa73276699ba72dc7b5c9d08deaf8cd88eec33c866341b120304432b89586d45d';constaliceEthWallet=newethers.Wallet(ALICE_ETH_PRIVATE_KEY,ethProvider);console.log('Alice ETH address:',aliceEthWallet.address);constBOB_ETH_PRIVATE_KEY=process.env.BOB_ETH_PRIVATE_KEY||'0x8e85561005f27d926af79a7ce3e76e75108a09ff2ab78bf65b5578d2e5d509bf';constbobEthWallet=newethers.Wallet(BOB_ETH_PRIVATE_KEY,ethProvider);console.log('Bob ETH address:',bobEthWallet.address);try{// --- Alice: Generate proof and hashlock ---console.log('\n--- Alice: Generate proof and hashlock ---');constproof=randomBytes(32);console.log('Proof (hex):',proof.toString('hex'));constfirstHash=createHash('sha256').update(proof).digest();constsecret=createHash('sha256').update(firstHash).digest();console.log('Secret (double SHA-256):',secret.toString('hex'));// --- Step 1. Alice: Lock ETH on Ethereum ---console.log('\n--- Step 1. Alice: Lock ETH on Ethereum ---');consthtlcAsAlice=newethers.Contract(HTLC_ADDRESS,HTLC_ABI,aliceEthWallet);consttimelock=Math.floor(Date.now()/1000)+72*60*60;console.log('Ethereum timelock (Unix):',timelock);constlockTx=awaithtlcAsAlice.newContract(bobEthWallet.address,'0x'+secret.toString('hex'),timelock,{value:ethers.parseEther('0.01')});console.log('Lock TX hash:',lockTx.hash);constlockReceipt=awaitlockTx.wait();console.log('Lock confirmed in block',lockReceipt.blockNumber);// Extract the contractId from the LogHTLCNew eventconstcontractId=lockReceipt.logs[0].topics[1];console.log('HTLC contract ID:',contractId);// --- Step 2. Bob: Create secret lock on Symbol ---console.log('\n--- Step 2. Bob: Create secret lock on Symbol ---');// Bob queries the Ethereum contract to get the hashlockconsthtlcAsBob=newethers.Contract(HTLC_ADDRESS,HTLC_ABI,bobEthWallet);constcontractInfo=awaithtlcAsBob.getContract(contractId);consthashlock=contractInfo.hashlock.slice(2);// strip 0x prefixconsole.log('Hashlock from chain:',hashlock);constlockDuration=5760n;// ~48h at 30s blocksconsole.log('Lock duration:',lockDuration.toString(),'blocks');constsecretLockTransaction=facade.transactionFactory.create({type:'secret_lock_transaction_v1',signerPublicKey:bobXymKeyPair.publicKey.toString(),deadline:(awaitgetNetworkTime()).addHours(2).timestamp,recipientAddress:aliceXymAddress.toString(),mosaic:{mosaicId:generateMosaicAliasId('symbol.xym'),amount:1_000000n// 1 XYM},duration:lockDuration,secret:hashlock,hashAlgorithm:'hash_256'});secretLockTransaction.fee=newmodels.Amount((awaitgetFeeMultiplier())*secretLockTransaction.size);// Sign and announceconstlockSignature=facade.signTransaction(bobXymKeyPair,secretLockTransaction);constlockPayload=facade.transactionFactory.static.attachSignature(secretLockTransaction,lockSignature);console.log('Built secret lock transaction:');console.dir(secretLockTransaction.toJson(),{colors:true});constlockHash=facade.hashTransaction(secretLockTransaction).toString();console.log('Secret lock transaction hash:',lockHash);awaitannounceTransaction(lockPayload,'/transactions','secret lock');awaitwaitForStatus(lockHash,'confirmed','Secret lock');// --- Step 3. Alice: Claim XYM on Symbol ---console.log('\n--- Step 3. Alice: Claim XYM on Symbol ---');constsecretProofTransaction=facade.transactionFactory.create({type:'secret_proof_transaction_v1',signerPublicKey:aliceXymKeyPair.publicKey.toString(),deadline:(awaitgetNetworkTime()).addHours(2).timestamp,recipientAddress:aliceXymAddress.toString(),secret:hashlock,hashAlgorithm:'hash_256',proof:proof});secretProofTransaction.fee=newmodels.Amount((awaitgetFeeMultiplier())*secretProofTransaction.size);// Sign and announceconstproofSignature=facade.signTransaction(aliceXymKeyPair,secretProofTransaction);constproofPayload=facade.transactionFactory.static.attachSignature(secretProofTransaction,proofSignature);console.log('Built secret proof transaction:');console.dir(secretProofTransaction.toJson(),{colors:true});constproofHash=facade.hashTransaction(secretProofTransaction).toString();console.log('Secret proof transaction hash:',proofHash);awaitannounceTransaction(proofPayload,'/transactions','secret proof');awaitwaitForStatus(proofHash,'confirmed','Secret proof');// --- Step 4. Bob: Withdraw ETH on Ethereum ---console.log('\n--- Step 4. Bob: Withdraw ETH on Ethereum ---');// Bob waits for Alice to reveal the proof on Symbol.constrevealedProof=awaitwaitForSecretProof(aliceXymAddress.toString(),hashlock);console.log('Proof from chain:',revealedProof.toString('hex'));constwithdrawTx=awaithtlcAsBob.withdraw(contractId,revealedProof);console.log('Withdraw TX hash:',withdrawTx.hash);constwithdrawReceipt=awaitwithdrawTx.wait();console.log('Withdraw confirmed in block',withdrawReceipt.blockNumber);console.log('\n--- Cross-chain swap complete ---');}catch(e){console.error(e.message);}
# Symbol accountsfacade=SymbolFacade('testnet')ALICE_XYM_PRIVATE_KEY=os.getenv('ALICE_XYM_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000000')alice_xym_key_pair=SymbolFacade.KeyPair(PrivateKey(ALICE_XYM_PRIVATE_KEY))alice_xym_address=facade.network.public_key_to_address(alice_xym_key_pair.public_key)print(f'Alice Symbol address: {alice_xym_address}')BOB_XYM_PRIVATE_KEY=os.getenv('BOB_XYM_PRIVATE_KEY','1111111111111111111111111111111111111111111111111111111111111111')bob_xym_key_pair=SymbolFacade.KeyPair(PrivateKey(BOB_XYM_PRIVATE_KEY))bob_xym_address=facade.network.public_key_to_address(bob_xym_key_pair.public_key)print(f'Bob Symbol address: {bob_xym_address}')# Ethereum accountsw3=Web3(Web3.HTTPProvider(ETH_RPC_URL))ALICE_ETH_PRIVATE_KEY=os.getenv('ALICE_ETH_PRIVATE_KEY','0xa73276699ba72dc7b5c9d08deaf8cd88eec33c866341b120304432b89586d45d')alice_eth_account=w3.eth.account.from_key(ALICE_ETH_PRIVATE_KEY)print(f'Alice ETH address: {alice_eth_account.address}')BOB_ETH_PRIVATE_KEY=os.getenv('BOB_ETH_PRIVATE_KEY','0x8e85561005f27d926af79a7ce3e76e75108a09ff2ab78bf65b5578d2e5d509bf')bob_eth_account=w3.eth.account.from_key(BOB_ETH_PRIVATE_KEY)print(f'Bob ETH address: {bob_eth_account.address}')
// Symbol accountsconstfacade=newSymbolFacade('testnet');// Alice (creates the ETH lock, claims XYM on Symbol)constALICE_XYM_PRIVATE_KEY=process.env.ALICE_XYM_PRIVATE_KEY||'0000000000000000000000000000000000000000000000000000000000000000';constaliceXymKeyPair=newSymbolFacade.KeyPair(newPrivateKey(ALICE_XYM_PRIVATE_KEY));constaliceXymAddress=facade.network.publicKeyToAddress(aliceXymKeyPair.publicKey);console.log('Alice Symbol address:',aliceXymAddress.toString());// Bob (creates the XYM lock, claims ETH on Ethereum)constBOB_XYM_PRIVATE_KEY=process.env.BOB_XYM_PRIVATE_KEY||'1111111111111111111111111111111111111111111111111111111111111111';constbobXymKeyPair=newSymbolFacade.KeyPair(newPrivateKey(BOB_XYM_PRIVATE_KEY));constbobXymAddress=facade.network.publicKeyToAddress(bobXymKeyPair.publicKey);console.log('Bob Symbol address:',bobXymAddress.toString());// Ethereum accountsconstethProvider=newethers.JsonRpcProvider(ETH_RPC_URL);constALICE_ETH_PRIVATE_KEY=process.env.ALICE_ETH_PRIVATE_KEY||'0xa73276699ba72dc7b5c9d08deaf8cd88eec33c866341b120304432b89586d45d';constaliceEthWallet=newethers.Wallet(ALICE_ETH_PRIVATE_KEY,ethProvider);console.log('Alice ETH address:',aliceEthWallet.address);constBOB_ETH_PRIVATE_KEY=process.env.BOB_ETH_PRIVATE_KEY||'0x8e85561005f27d926af79a7ce3e76e75108a09ff2ab78bf65b5578d2e5d509bf';constbobEthWallet=newethers.Wallet(BOB_ETH_PRIVATE_KEY,ethProvider);console.log('Bob ETH address:',bobEthWallet.address);
print('\n--- Step 1. Alice: Lock ETH on Ethereum ---')htlc=w3.eth.contract(address=HTLC_ADDRESS,abi=HTLC_ABI)timelock=int(time.time())+72*60*60print(f'Ethereum timelock (Unix): {timelock}')lock_call=htlc.functions.newContract(bob_eth_account.address,secret,timelock)lock_tx=lock_call.build_transaction({'from':alice_eth_account.address,'value':w3.to_wei(0.01,'ether'),'nonce':w3.eth.get_transaction_count(alice_eth_account.address)})signed_lock_tx=alice_eth_account.sign_transaction(lock_tx)lock_tx_hash=w3.eth.send_raw_transaction(signed_lock_tx.raw_transaction)print(f'Lock TX hash: {lock_tx_hash.hex()}')lock_receipt=w3.eth.wait_for_transaction_receipt(lock_tx_hash)print(f'Lock confirmed in block {lock_receipt.blockNumber}')# Extract the contractId from the LogHTLCNew eventcontract_id=lock_receipt.logs[0].topics[1]print(f'HTLC contract ID: {contract_id.hex()}')
console.log('\n--- Step 1. Alice: Lock ETH on Ethereum ---');consthtlcAsAlice=newethers.Contract(HTLC_ADDRESS,HTLC_ABI,aliceEthWallet);consttimelock=Math.floor(Date.now()/1000)+72*60*60;console.log('Ethereum timelock (Unix):',timelock);constlockTx=awaithtlcAsAlice.newContract(bobEthWallet.address,'0x'+secret.toString('hex'),timelock,{value:ethers.parseEther('0.01')});console.log('Lock TX hash:',lockTx.hash);constlockReceipt=awaitlockTx.wait();console.log('Lock confirmed in block',lockReceipt.blockNumber);// Extract the contractId from the LogHTLCNew eventconstcontractId=lockReceipt.logs[0].topics[1];console.log('HTLC contract ID:',contractId);
print('\n--- Step 2. Bob: Create secret lock on Symbol ---')# Bob queries the Ethereum contract to get the hashlockcontract_info=htlc.functions.getContract(contract_id).call()hashlock=contract_info[3]# hashlock fieldprint(f'Hashlock from chain: {hashlock.hex()}')lock_duration=5760# ~48h at 30s blocksprint(f'Lock duration: {lock_duration} blocks')secret_lock_transaction=facade.transaction_factory.create({'type':'secret_lock_transaction_v1','signer_public_key':bob_xym_key_pair.public_key,'deadline':get_network_time().add_hours(2).timestamp,'recipient_address':alice_xym_address,'mosaic':{'mosaic_id':generate_mosaic_alias_id('symbol.xym'),'amount':1_000000# 1 XYM},'duration':lock_duration,'secret':Hash256(hashlock),'hash_algorithm':'hash_256'})secret_lock_transaction.fee=Amount(get_fee_multiplier()*secret_lock_transaction.size)# Sign and announcelock_signature=facade.sign_transaction(bob_xym_key_pair,secret_lock_transaction)lock_payload=facade.transaction_factory.attach_signature(secret_lock_transaction,lock_signature)print('Built secret lock transaction:')print(json.dumps(secret_lock_transaction.to_json(),indent=2))lock_hash=facade.hash_transaction(secret_lock_transaction)print(f'Secret lock transaction hash: {lock_hash}')announce_transaction(lock_payload,'/transactions','secret lock')wait_for_status(lock_hash,'confirmed','Secret lock')
console.log('\n--- Step 2. Bob: Create secret lock on Symbol ---');// Bob queries the Ethereum contract to get the hashlockconsthtlcAsBob=newethers.Contract(HTLC_ADDRESS,HTLC_ABI,bobEthWallet);constcontractInfo=awaithtlcAsBob.getContract(contractId);consthashlock=contractInfo.hashlock.slice(2);// strip 0x prefixconsole.log('Hashlock from chain:',hashlock);constlockDuration=5760n;// ~48h at 30s blocksconsole.log('Lock duration:',lockDuration.toString(),'blocks');constsecretLockTransaction=facade.transactionFactory.create({type:'secret_lock_transaction_v1',signerPublicKey:bobXymKeyPair.publicKey.toString(),deadline:(awaitgetNetworkTime()).addHours(2).timestamp,recipientAddress:aliceXymAddress.toString(),mosaic:{mosaicId:generateMosaicAliasId('symbol.xym'),amount:1_000000n// 1 XYM},duration:lockDuration,secret:hashlock,hashAlgorithm:'hash_256'});secretLockTransaction.fee=newmodels.Amount((awaitgetFeeMultiplier())*secretLockTransaction.size);// Sign and announceconstlockSignature=facade.signTransaction(bobXymKeyPair,secretLockTransaction);constlockPayload=facade.transactionFactory.static.attachSignature(secretLockTransaction,lockSignature);console.log('Built secret lock transaction:');console.dir(secretLockTransaction.toJson(),{colors:true});constlockHash=facade.hashTransaction(secretLockTransaction).toString();console.log('Secret lock transaction hash:',lockHash);awaitannounceTransaction(lockPayload,'/transactions','secret lock');awaitwaitForStatus(lockHash,'confirmed','Secret lock');
print('\n--- Step 4. Bob: Withdraw ETH on Ethereum ---')# Bob waits for Alice to reveal the proof on Symbol.revealed_proof=wait_for_secret_proof(alice_xym_address,hashlock)print(f'Proof from chain: {revealed_proof.hex()}')withdraw_call=htlc.functions.withdraw(contract_id,revealed_proof)withdraw_tx=withdraw_call.build_transaction({'from':bob_eth_account.address,'nonce':w3.eth.get_transaction_count(bob_eth_account.address)})signed_withdraw_tx=bob_eth_account.sign_transaction(withdraw_tx)withdraw_tx_hash=w3.eth.send_raw_transaction(signed_withdraw_tx.raw_transaction)print(f'Withdraw TX hash: {withdraw_tx_hash.hex()}')withdraw_receipt=w3.eth.wait_for_transaction_receipt(withdraw_tx_hash)print(f'Withdraw confirmed in block {withdraw_receipt.blockNumber}')
console.log('\n--- Step 4. Bob: Withdraw ETH on Ethereum ---');// Bob waits for Alice to reveal the proof on Symbol.constrevealedProof=awaitwaitForSecretProof(aliceXymAddress.toString(),hashlock);console.log('Proof from chain:',revealedProof.toString('hex'));constwithdrawTx=awaithtlcAsBob.withdraw(contractId,revealedProof);console.log('Withdraw TX hash:',withdrawTx.hash);constwithdrawReceipt=awaitwithdrawTx.wait();console.log('Withdraw confirmed in block',withdrawReceipt.blockNumber);