github.com/0chain/gosdk@v1.17.11/zcnbridge/bridge.go (about) 1 package zcnbridge 2 3 import ( 4 "context" 5 "encoding/hex" 6 "fmt" 7 "math/big" 8 "strings" 9 "time" 10 11 "github.com/0chain/gosdk/zcnbridge/ethereum/uniswapnetwork" 12 "github.com/0chain/gosdk/zcnbridge/ethereum/uniswaprouter" 13 14 "github.com/ybbus/jsonrpc/v3" 15 16 "github.com/0chain/gosdk/zcnbridge/ethereum/zcntoken" 17 hdw "github.com/0chain/gosdk/zcncore/ethhdwallet" 18 "github.com/spf13/viper" 19 20 "gopkg.in/natefinch/lumberjack.v2" 21 22 "github.com/0chain/gosdk/core/logger" 23 "github.com/0chain/gosdk/zcnbridge/ethereum" 24 "github.com/0chain/gosdk/zcnbridge/ethereum/authorizers" 25 "github.com/0chain/gosdk/zcnbridge/ethereum/bridge" 26 "github.com/0chain/gosdk/zcnbridge/ethereum/nftconfig" 27 "github.com/0chain/gosdk/zcnbridge/log" 28 "github.com/0chain/gosdk/zcncore" 29 30 "github.com/0chain/gosdk/zcnbridge/transaction" 31 "github.com/0chain/gosdk/zcnbridge/wallet" 32 "github.com/0chain/gosdk/zcnbridge/zcnsc" 33 eth "github.com/ethereum/go-ethereum" 34 "github.com/ethereum/go-ethereum/accounts" 35 "github.com/ethereum/go-ethereum/accounts/abi/bind" 36 "github.com/ethereum/go-ethereum/common" 37 "github.com/ethereum/go-ethereum/core/types" 38 "github.com/ethereum/go-ethereum/crypto" 39 "github.com/pkg/errors" 40 "go.uber.org/zap" 41 ) 42 43 var Logger logger.Logger 44 var defaultLogLevel = logger.DEBUG 45 46 func init() { 47 Logger.Init(defaultLogLevel, "zcnbridge-sdk") 48 49 Logger.SetLevel(logger.DEBUG) 50 ioWriter := &lumberjack.Logger{ 51 Filename: "bridge.log", 52 MaxSize: 100, // MB 53 MaxBackups: 5, // number of backups 54 MaxAge: 28, //days 55 LocalTime: false, 56 Compress: false, // disabled by default 57 } 58 Logger.SetLogFile(ioWriter, true) 59 } 60 61 var ( 62 DefaultClientIDEncoder = func(id string) []byte { 63 result, err := hex.DecodeString(id) 64 if err != nil { 65 Logger.Fatal(err) 66 } 67 return result 68 } 69 ) 70 71 // CreateSignedTransactionFromKeyStore creates signed transaction from key store 72 // - client - Ethereum client 73 // - gasLimitUnits - gas limit in units 74 func (b *BridgeClient) CreateSignedTransactionFromKeyStore(client EthereumClient, gasLimitUnits uint64) *bind.TransactOpts { 75 var ( 76 signerAddress = common.HexToAddress(b.EthereumAddress) 77 password = b.Password 78 ) 79 80 signer := accounts.Account{ 81 Address: signerAddress, 82 } 83 84 signerAcc, err := b.keyStore.Find(signer) 85 if err != nil { 86 Logger.Fatal(errors.Wrapf(err, "signer: %s", signerAddress.Hex())) 87 } 88 89 chainID, err := client.ChainID(context.Background()) 90 if err != nil { 91 Logger.Fatal(errors.Wrap(err, "failed to get chain ID")) 92 } 93 94 nonce, err := client.PendingNonceAt(context.Background(), signerAddress) 95 if err != nil { 96 Logger.Fatal(err) 97 } 98 99 gasPriceWei, err := client.SuggestGasPrice(context.Background()) 100 if err != nil { 101 Logger.Fatal(err) 102 } 103 104 err = b.keyStore.TimedUnlock(signer, password, time.Second*2) 105 if err != nil { 106 Logger.Fatal(err) 107 } 108 109 opts, err := bind.NewKeyStoreTransactorWithChainID(b.keyStore.GetEthereumKeyStore(), signerAcc, chainID) 110 if err != nil { 111 Logger.Fatal(err) 112 } 113 114 opts.Nonce = big.NewInt(int64(nonce)) 115 opts.GasLimit = gasLimitUnits // in units 116 opts.GasPrice = gasPriceWei // wei 117 118 return opts 119 } 120 121 // AddEthereumAuthorizer Adds authorizer to Ethereum bridge. Only contract deployer can call this method 122 // - ctx go context instance to run the transaction 123 // - address Ethereum address of the authorizer 124 func (b *BridgeClient) AddEthereumAuthorizer(ctx context.Context, address common.Address) (*types.Transaction, error) { 125 instance, transactOpts, err := b.prepareAuthorizers(ctx, "addAuthorizers", address) 126 if err != nil { 127 return nil, errors.Wrap(err, "failed to prepare bridge") 128 } 129 130 tran, err := instance.AddAuthorizers(transactOpts, address) 131 if err != nil { 132 msg := "failed to execute AddAuthorizers transaction to ClientID = %s with amount = %s" 133 return nil, errors.Wrapf(err, msg, zcncore.GetClientWalletID(), address.String()) 134 } 135 136 return tran, err 137 } 138 139 // RemoveEthereumAuthorizer Removes authorizer from Ethereum bridge. Only contract deployer can call this method 140 // - ctx go context instance to run the transaction 141 // - address Ethereum address of the authorizer 142 func (b *BridgeClient) RemoveEthereumAuthorizer(ctx context.Context, address common.Address) (*types.Transaction, error) { 143 instance, transactOpts, err := b.prepareAuthorizers(ctx, "removeAuthorizers", address) 144 if err != nil { 145 return nil, errors.Wrap(err, "failed to prepare bridge") 146 } 147 148 tran, err := instance.RemoveAuthorizers(transactOpts, address) 149 if err != nil { 150 msg := "failed to execute RemoveAuthorizers transaction to ClientID = %s with amount = %s" 151 return nil, errors.Wrapf(err, msg, zcncore.GetClientWalletID(), address.String()) 152 } 153 154 return tran, err 155 } 156 157 // AddEthereumAuthorizers add bridge authorizers to the Ethereum authorizers contract 158 // - configDir - configuration directory 159 func (b *BridgeClient) AddEthereumAuthorizers(configDir string) { 160 cfg := viper.New() 161 cfg.AddConfigPath(configDir) 162 cfg.SetConfigName("authorizers") 163 if err := cfg.ReadInConfig(); err != nil { 164 fmt.Println(err) 165 return 166 } 167 168 mnemonics := cfg.GetStringSlice("authorizers") 169 170 for _, mnemonic := range mnemonics { 171 wallet, err := hdw.NewFromMnemonic(mnemonic) 172 if err != nil { 173 fmt.Printf("failed to read mnemonic: %v", err) 174 continue 175 } 176 177 pathD := hdw.MustParseDerivationPath("m/44'/60'/0'/0/0") 178 account, err := wallet.Derive(pathD, true) 179 if err != nil { 180 fmt.Println(err) 181 continue 182 } 183 184 transaction, err := b.AddEthereumAuthorizer(context.TODO(), account.Address) 185 if err != nil || transaction == nil { 186 fmt.Printf("AddAuthorizer error: %v, Address: %s", err, account.Address.Hex()) 187 continue 188 } 189 190 status, err := ConfirmEthereumTransaction(transaction.Hash().String(), 100, time.Second*10) 191 if err != nil { 192 fmt.Println(err) 193 } 194 195 if status == 1 { 196 fmt.Printf("Authorizer has been added: %s\n", mnemonic) 197 } else { 198 fmt.Printf("Authorizer has failed to be added: %s\n", mnemonic) 199 } 200 } 201 } 202 203 func (b *BridgeClient) prepareNFTConfig(ctx context.Context, method string, params ...interface{}) (*nftconfig.NFTConfig, *bind.TransactOpts, error) { 204 // To (contract) 205 contractAddress := common.HexToAddress(b.NFTConfigAddress) 206 207 // Get ABI of the contract 208 abi, err := nftconfig.NFTConfigMetaData.GetAbi() 209 if err != nil { 210 return nil, nil, errors.Wrap(err, "failed to get nftconfig ABI") 211 } 212 213 // Pack the method argument 214 pack, err := abi.Pack(method, params...) 215 if err != nil { 216 return nil, nil, errors.Wrap(err, "failed to pack arguments") 217 } 218 219 from := common.HexToAddress(b.EthereumAddress) 220 221 // Gas limits in units 222 gasLimitUnits, err := b.ethereumClient.EstimateGas(ctx, eth.CallMsg{ 223 To: &contractAddress, 224 From: from, 225 Data: pack, 226 }) 227 if err != nil { 228 return nil, nil, errors.Wrap(err, "failed to estimate gas") 229 } 230 231 // Update gas limits + 10% 232 gasLimitUnits = addPercents(gasLimitUnits, 10).Uint64() 233 234 transactOpts := b.CreateSignedTransactionFromKeyStore(b.ethereumClient, gasLimitUnits) 235 236 // NFTConfig instance 237 cfg, err := nftconfig.NewNFTConfig(contractAddress, b.ethereumClient) 238 if err != nil { 239 return nil, nil, errors.Wrap(err, "failed to create nftconfig instance") 240 } 241 242 return cfg, transactOpts, nil 243 } 244 245 // EncodePackInt do abi.encodedPack(string, int), it is used for setting plan id for royalty 246 // - key key for the plan 247 // - param plan id 248 func EncodePackInt64(key string, param int64) common.Hash { 249 return crypto.Keccak256Hash( 250 []byte(key), 251 common.LeftPadBytes(big.NewInt(param).Bytes(), 32), 252 ) 253 } 254 255 // NFTConfigSetUint256 sets a uint256 field in the NFT config, given the key as a string 256 // - ctx go context instance to run the transaction 257 // - key key for this field 258 // - value value to set 259 func (b *BridgeClient) NFTConfigSetUint256(ctx context.Context, key string, value int64) (*types.Transaction, error) { 260 kkey := crypto.Keccak256Hash([]byte(key)) 261 return b.NFTConfigSetUint256Raw(ctx, kkey, value) 262 } 263 264 // NFTConfigSetUint256Raw sets a uint256 field in the NFT config, given the key as a Keccak256 hash 265 // - ctx go context instance to run the transaction 266 // - key key for this field (hased) 267 // - value value to set 268 func (b *BridgeClient) NFTConfigSetUint256Raw(ctx context.Context, key common.Hash, value int64) (*types.Transaction, error) { 269 if value < 0 { 270 return nil, errors.New("value must be greater than zero") 271 } 272 273 v := big.NewInt(value) 274 Logger.Debug("NFT config setUint256", zap.String("key", key.String()), zap.Any("value", v)) 275 instance, transactOpts, err := b.prepareNFTConfig(ctx, "setUint256", key, v) 276 if err != nil { 277 return nil, errors.Wrap(err, "failed to prepare bridge") 278 } 279 280 tran, err := instance.SetUint256(transactOpts, key, v) 281 if err != nil { 282 msg := "failed to execute setUint256 transaction to ClientID = %s with key = %s, value = %v" 283 return nil, errors.Wrapf(err, msg, zcncore.GetClientWalletID(), key, v) 284 } 285 286 return tran, err 287 } 288 289 // NFTConfigGetUint256 retrieves a uint256 field in the NFT config, given the key as a string 290 // - ctx go context instance to run the transaction 291 // - key key for this field 292 // - keyParam additional key parameter, only the first item is used 293 func (b *BridgeClient) NFTConfigGetUint256(ctx context.Context, key string, keyParam ...int64) (string, int64, error) { 294 kkey := crypto.Keccak256Hash([]byte(key)) 295 if len(keyParam) > 0 { 296 kkey = EncodePackInt64(key, keyParam[0]) 297 } 298 299 contractAddress := common.HexToAddress(b.NFTConfigAddress) 300 301 cfg, err := nftconfig.NewNFTConfig(contractAddress, b.ethereumClient) 302 if err != nil { 303 return "", 0, errors.Wrap(err, "failed to create NFT config instance") 304 } 305 306 v, err := cfg.GetUint256(nil, kkey) 307 if err != nil { 308 Logger.Error("NFTConfig GetUint256 FAILED", zap.Error(err)) 309 msg := "failed to execute getUint256 call, key = %s" 310 return "", 0, errors.Wrapf(err, msg, kkey) 311 } 312 return kkey.String(), v.Int64(), err 313 } 314 315 // NFTConfigSetAddress sets an address field in the NFT config, given the key as a string 316 // - ctx go context instance to run the transaction 317 // - key key for this field 318 // - address address to set 319 func (b *BridgeClient) NFTConfigSetAddress(ctx context.Context, key, address string) (*types.Transaction, error) { 320 kkey := crypto.Keccak256Hash([]byte(key)) 321 // return b.NFTConfigSetAddress(ctx, kkey, address) 322 323 Logger.Debug("NFT config setAddress", 324 zap.String("key", kkey.String()), 325 zap.String("address", address)) 326 327 addr := common.HexToAddress(address) 328 instance, transactOpts, err := b.prepareNFTConfig(ctx, "setAddress", kkey, addr) 329 if err != nil { 330 return nil, errors.Wrap(err, "failed to prepare bridge") 331 } 332 333 tran, err := instance.SetAddress(transactOpts, kkey, addr) 334 if err != nil { 335 msg := "failed to execute setAddress transaction to ClientID = %s with key = %s, value = %v" 336 return nil, errors.Wrapf(err, msg, zcncore.GetClientWalletID(), key, address) 337 } 338 339 return tran, err 340 } 341 342 // NFTConfigGetAddress retrieves an address field in the NFT config, given the key as a string 343 // - ctx go context instance to run the transaction 344 // - key key for this field 345 func (b *BridgeClient) NFTConfigGetAddress(ctx context.Context, key string) (string, string, error) { 346 kkey := crypto.Keccak256Hash([]byte(key)) 347 348 contractAddress := common.HexToAddress(b.NFTConfigAddress) 349 350 cfg, err := nftconfig.NewNFTConfig(contractAddress, b.ethereumClient) 351 if err != nil { 352 return "", "", errors.Wrap(err, "failed to create NFT config instance") 353 } 354 355 v, err := cfg.GetAddress(nil, kkey) 356 if err != nil { 357 Logger.Error("NFTConfig GetAddress FAILED", zap.Error(err)) 358 msg := "failed to execute getAddress call, key = %s" 359 return "", "", errors.Wrapf(err, msg, kkey) 360 } 361 return kkey.String(), v.String(), err 362 } 363 364 // IncreaseBurnerAllowance Increases allowance for bridge contract address to transfer 365 // ERC-20 tokens on behalf of the zcntoken owner to the Burn TokenPool 366 // During the burn the script transfers amount from zcntoken owner to the bridge burn zcntoken pool 367 // Example: owner wants to burn some amount. 368 // The contract will transfer some amount from owner address to the pool. 369 // So the owner must call IncreaseAllowance of the WZCN zcntoken with 2 parameters: 370 // spender address which is the bridge contract and amount to be burned (transferred) 371 // Token signature: "increaseApproval(address,uint256)" 372 // - ctx go context instance to run the transaction 373 // - allowanceAmount amount to increase 374 // 375 //nolint:funlen 376 func (b *BridgeClient) IncreaseBurnerAllowance(ctx context.Context, allowanceAmount uint64) (*types.Transaction, error) { 377 if allowanceAmount <= 0 { 378 return nil, errors.New("amount must be greater than zero") 379 } 380 381 // 1. Data Parameter (spender) 382 spenderAddress := common.HexToAddress(b.BridgeAddress) 383 384 // 2. Data Parameter (amount) 385 amount := big.NewInt(int64(allowanceAmount)) 386 387 tokenAddress := common.HexToAddress(b.TokenAddress) 388 389 tokenInstance, transactOpts, err := b.prepareToken(ctx, "increaseApproval", tokenAddress, spenderAddress, amount) 390 if err != nil { 391 return nil, errors.Wrap(err, "failed to prepare zcntoken") 392 } 393 394 Logger.Info( 395 "Starting IncreaseApproval", 396 zap.String("zcntoken", tokenAddress.String()), 397 zap.String("spender", spenderAddress.String()), 398 zap.Int64("amount", amount.Int64()), 399 ) 400 401 tran, err := tokenInstance.IncreaseApproval(transactOpts, spenderAddress, amount) 402 if err != nil { 403 Logger.Error( 404 "IncreaseApproval FAILED", 405 zap.String("zcntoken", tokenAddress.String()), 406 zap.String("spender", spenderAddress.String()), 407 zap.Int64("amount", amount.Int64()), 408 zap.Error(err)) 409 410 return nil, errors.Wrapf(err, "failed to send `IncreaseApproval` transaction") 411 } 412 413 Logger.Info( 414 "Posted IncreaseApproval", 415 zap.String("hash", tran.Hash().String()), 416 zap.String("zcntoken", tokenAddress.String()), 417 zap.String("spender", spenderAddress.String()), 418 zap.Int64("amount", amount.Int64()), 419 ) 420 421 return tran, nil 422 } 423 424 // GetTokenBalance returns balance of the current client for the zcntoken address 425 func (b *BridgeClient) GetTokenBalance() (*big.Int, error) { 426 // 1. Token address parameter 427 of := common.HexToAddress(b.TokenAddress) 428 429 // 2. User's Ethereum wallet address parameter 430 from := common.HexToAddress(b.EthereumAddress) 431 432 tokenInstance, err := zcntoken.NewToken(of, b.ethereumClient) 433 if err != nil { 434 return nil, errors.Wrap(err, "failed to initialize zcntoken instance") 435 } 436 437 wei, err := tokenInstance.BalanceOf(&bind.CallOpts{}, from) 438 if err != nil { 439 return nil, errors.Wrapf(err, "failed to call `BalanceOf` for %s", b.EthereumAddress) 440 } 441 442 return wei, nil 443 } 444 445 // VerifyZCNTransaction verifies 0CHain transaction 446 // - ctx go context instance to run the transaction 447 // - hash transaction hash 448 func (b *BridgeClient) VerifyZCNTransaction(ctx context.Context, hash string) (transaction.Transaction, error) { 449 return transaction.Verify(ctx, hash) 450 } 451 452 // SignWithEthereumChain signs the digest with Ethereum chain signer taking key from the current user key storage 453 // - message message to sign 454 func (b *BridgeClient) SignWithEthereumChain(message string) ([]byte, error) { 455 hash := crypto.Keccak256Hash([]byte(message)) 456 457 signer := accounts.Account{ 458 Address: common.HexToAddress(b.EthereumAddress), 459 } 460 461 signerAcc, err := b.keyStore.Find(signer) 462 if err != nil { 463 Logger.Fatal(err) 464 } 465 466 signature, err := b.keyStore.SignHash(signerAcc, hash.Bytes()) 467 if err != nil { 468 return nil, err 469 } 470 471 if err != nil { 472 return []byte{}, errors.Wrap(err, "failed to sign the message") 473 } 474 475 return signature, nil 476 } 477 478 // GetUserNonceMinted Returns nonce for a specified Ethereum address 479 // - ctx go context instance to run the transaction 480 // - rawEthereumAddress Ethereum address 481 func (b *BridgeClient) GetUserNonceMinted(ctx context.Context, rawEthereumAddress string) (*big.Int, error) { 482 ethereumAddress := common.HexToAddress(rawEthereumAddress) 483 484 contractAddress := common.HexToAddress(b.BridgeAddress) 485 486 bridgeInstance, err := bridge.NewBridge(contractAddress, b.ethereumClient) 487 if err != nil { 488 return nil, errors.Wrap(err, "failed to create bridge instance") 489 } 490 491 var nonce *big.Int 492 493 nonce, err = bridgeInstance.GetUserNonceMinted(nil, ethereumAddress) 494 if err != nil { 495 Logger.Error("GetUserNonceMinted FAILED", zap.Error(err)) 496 msg := "failed to execute GetUserNonceMinted call, ethereumAddress = %s" 497 return nil, errors.Wrapf(err, msg, rawEthereumAddress) 498 } 499 500 return nonce, err 501 } 502 503 // ResetUserNonceMinted Resets nonce for a specified Ethereum address 504 // - ctx go context instance to run the transaction 505 func (b *BridgeClient) ResetUserNonceMinted(ctx context.Context) (*types.Transaction, error) { 506 bridgeInstance, transactOpts, err := b.prepareBridge(ctx, b.EthereumAddress, "resetUserNonceMinted") 507 if err != nil { 508 return nil, errors.Wrap(err, "failed to prepare bridge") 509 } 510 511 tran, err := bridgeInstance.ResetUserNonceMinted(transactOpts) 512 if err != nil { 513 Logger.Error("ResetUserNonceMinted FAILED", zap.Error(err)) 514 msg := "failed to execute ResetUserNonceMinted call, ethereumAddress = %s" 515 return nil, errors.Wrapf(err, msg, b.EthereumAddress) 516 } 517 518 Logger.Info( 519 "Posted ResetUserMintedNonce", 520 zap.String("ethereumWallet", b.EthereumAddress), 521 ) 522 523 return tran, err 524 } 525 526 // MintWZCN Mint ZCN tokens on behalf of the 0ZCN client 527 // - ctx go context instance to run the transaction 528 // - payload received from authorizers 529 // 530 // ERC20 signature: "mint(address,uint256,bytes,uint256,bytes[])" 531 func (b *BridgeClient) MintWZCN(ctx context.Context, payload *ethereum.MintPayload) (*types.Transaction, error) { 532 if DefaultClientIDEncoder == nil { 533 return nil, errors.New("DefaultClientIDEncoder must be setup") 534 } 535 536 // 1. Burned amount parameter 537 amount := new(big.Int) 538 amount.SetInt64(payload.Amount) // wei 539 540 // 2. Transaction ID Parameter of burn operation (zcnTxd string as []byte) 541 zcnTxd := DefaultClientIDEncoder(payload.ZCNTxnID) 542 543 // 3. Nonce Parameter generated during burn operation 544 nonce := new(big.Int) 545 nonce.SetInt64(payload.Nonce) 546 547 // 4. Signature 548 // For requirements from ERC20 authorizer, the signature length must be 65 549 var sigs [][]byte 550 for _, signature := range payload.Signatures { 551 sigs = append(sigs, signature.Signature) 552 } 553 554 // 5. To Ethereum address 555 556 toAddress := common.HexToAddress(payload.To) 557 558 bridgeInstance, transactOpts, err := b.prepareBridge(ctx, payload.To, "mint", toAddress, amount, zcnTxd, nonce, sigs) 559 if err != nil { 560 return nil, errors.Wrap(err, "failed to prepare bridge") 561 } 562 563 Logger.Info( 564 "Staring Mint WZCN", 565 zap.Int64("amount", amount.Int64()), 566 zap.String("zcnTxd", string(zcnTxd)), 567 zap.String("nonce", nonce.String())) 568 569 var tran *types.Transaction 570 tran, err = bridgeInstance.Mint(transactOpts, toAddress, amount, zcnTxd, nonce, sigs) 571 if err != nil { 572 Logger.Error("Mint WZCN FAILED", zap.Error(err)) 573 msg := "failed to execute MintWZCN transaction, amount = %s, ZCN TrxID = %s" 574 return nil, errors.Wrapf(err, msg, amount, zcnTxd) 575 } 576 577 Logger.Info( 578 "Posted Mint WZCN", 579 zap.String("hash", tran.Hash().String()), 580 zap.Int64("amount", amount.Int64()), 581 zap.String("zcnTxd", string(zcnTxd)), 582 zap.String("nonce", nonce.String()), 583 ) 584 585 return tran, err 586 } 587 588 // BurnWZCN Burns WZCN tokens on behalf of the 0ZCN client 589 // - ctx go context instance to run the transaction 590 // - amountTokens amount of tokens to burn 591 // 592 // ERC20 signature: "burn(uint256,bytes)" 593 func (b *BridgeClient) BurnWZCN(ctx context.Context, amountTokens uint64) (*types.Transaction, error) { 594 if DefaultClientIDEncoder == nil { 595 return nil, errors.New("DefaultClientIDEncoder must be setup") 596 } 597 598 // 1. Data Parameter (amount to burn) 599 clientID := DefaultClientIDEncoder(zcncore.GetClientWalletID()) 600 601 // 2. Data Parameter (signature) 602 amount := new(big.Int) 603 amount.SetInt64(int64(amountTokens)) 604 605 bridgeInstance, transactOpts, err := b.prepareBridge(ctx, b.EthereumAddress, "burn", amount, clientID) 606 if err != nil { 607 return nil, errors.Wrap(err, "failed to prepare bridge") 608 } 609 610 Logger.Info( 611 "Staring Burn WZCN", 612 zap.Int64("amount", amount.Int64()), 613 ) 614 615 tran, err := bridgeInstance.Burn(transactOpts, amount, clientID) 616 if err != nil { 617 msg := "failed to execute Burn WZCN transaction to ClientID = %s with amount = %s" 618 return nil, errors.Wrapf(err, msg, zcncore.GetClientWalletID(), amount) 619 } 620 621 Logger.Info( 622 "Posted Burn WZCN", 623 zap.String("clientID", zcncore.GetClientWalletID()), 624 zap.Int64("amount", amount.Int64()), 625 ) 626 627 return tran, err 628 } 629 630 // MintZCN mints ZCN tokens after receiving proof-of-burn of WZCN tokens 631 // - ctx go context instance to run the transaction 632 // - payload received from authorizers 633 func (b *BridgeClient) MintZCN(ctx context.Context, payload *zcnsc.MintPayload) (string, error) { 634 trx, err := b.transactionProvider.NewTransactionEntity(0) 635 if err != nil { 636 log.Logger.Fatal("failed to create new transaction", zap.Error(err)) 637 } 638 639 Logger.Info( 640 "Starting MINT smart contract", 641 zap.String("sc address", wallet.ZCNSCSmartContractAddress), 642 zap.String("function", wallet.MintFunc), 643 zap.Int64("mint amount", int64(payload.Amount))) 644 645 hash, err := trx.ExecuteSmartContract( 646 ctx, 647 wallet.ZCNSCSmartContractAddress, 648 wallet.MintFunc, 649 payload, 650 0) 651 652 if err != nil { 653 return "", errors.Wrap(err, fmt.Sprintf("failed to execute smart contract, hash = %s", hash)) 654 } 655 656 Logger.Info( 657 "Mint ZCN transaction", 658 zap.String("hash", hash), 659 zap.Int64("mint amount", int64(payload.Amount))) 660 661 return hash, nil 662 } 663 664 // BurnZCN burns ZCN tokens before conversion from ZCN to WZCN as a first step 665 // - ctx go context instance to run the transaction 666 // - amount amount of tokens to burn 667 // - txnfee transaction fee 668 func (b *BridgeClient) BurnZCN(ctx context.Context, amount, txnfee uint64) (transaction.Transaction, error) { 669 payload := zcnsc.BurnPayload{ 670 EthereumAddress: b.EthereumAddress, 671 } 672 673 trx, err := b.transactionProvider.NewTransactionEntity(txnfee) 674 if err != nil { 675 log.Logger.Fatal("failed to create new transaction", zap.Error(err)) 676 } 677 678 Logger.Info( 679 "Starting BURN smart contract", 680 zap.String("sc address", wallet.ZCNSCSmartContractAddress), 681 zap.String("function", wallet.BurnFunc), 682 zap.Uint64("burn amount", amount), 683 ) 684 685 var hash string 686 hash, err = trx.ExecuteSmartContract( 687 ctx, 688 wallet.ZCNSCSmartContractAddress, 689 wallet.BurnFunc, 690 payload, 691 amount, 692 ) 693 694 if err != nil { 695 Logger.Error("Burn ZCN transaction FAILED", zap.Error(err)) 696 return trx, errors.Wrap(err, fmt.Sprintf("failed to execute smart contract, hash = %s", hash)) 697 } 698 699 err = trx.Verify(context.Background()) 700 if err != nil { 701 return trx, errors.Wrap(err, fmt.Sprintf("failed to verify smart contract, hash = %s", hash)) 702 } 703 704 Logger.Info( 705 "Burn ZCN transaction", 706 zap.String("hash", hash), 707 zap.Uint64("burn amount", amount), 708 zap.Uint64("amount", amount), 709 ) 710 711 return trx, nil 712 } 713 714 // ApproveUSDCSwap provides opportunity to approve swap operation for ERC20 tokens 715 // - ctx go context instance to run the transaction 716 // - source source amount 717 func (b *BridgeClient) ApproveUSDCSwap(ctx context.Context, source uint64) (*types.Transaction, error) { 718 // 1. USDC token smart contract address 719 tokenAddress := common.HexToAddress(UsdcTokenAddress) 720 721 // 2. Swap source amount parameter. 722 sourceInt := big.NewInt(int64(source)) 723 724 // 3. User's Ethereum wallet address parameter 725 spenderAddress := common.HexToAddress(b.UniswapAddress) 726 727 tokenInstance, transactOpts, err := b.prepareToken(ctx, "approve", tokenAddress, spenderAddress, sourceInt) 728 if err != nil { 729 return nil, errors.Wrap(err, "failed to prepare usdctoken") 730 } 731 732 Logger.Info( 733 "Starting ApproveUSDCSwap", 734 zap.String("usdctoken", tokenAddress.String()), 735 zap.String("spender", spenderAddress.String()), 736 zap.Int64("source", sourceInt.Int64()), 737 ) 738 739 var tran *types.Transaction 740 741 tran, err = tokenInstance.Approve(transactOpts, spenderAddress, sourceInt) 742 if err != nil { 743 return nil, errors.Wrap(err, "failed to execute approve transaction") 744 } 745 746 return tran, nil 747 } 748 749 // GetETHSwapAmount retrieves ETH swap amount from the given source. 750 // - ctx go context instance to run the transaction 751 // - source source amount 752 func (b *BridgeClient) GetETHSwapAmount(ctx context.Context, source uint64) (*big.Int, error) { 753 // 1. Uniswap smart contract address 754 contractAddress := common.HexToAddress(UniswapRouterAddress) 755 756 // 2. User's Ethereum wallet address parameter 757 from := common.HexToAddress(b.EthereumAddress) 758 759 // 3. Swap source amount parameter. 760 sourceInt := big.NewInt(int64(source)) 761 762 // 3. Swap path parameter. 763 path := []common.Address{ 764 common.HexToAddress(WethTokenAddress), 765 common.HexToAddress(b.TokenAddress)} 766 767 uniswapRouterInstance, err := uniswaprouter.NewUniswaprouter(contractAddress, b.ethereumClient) 768 if err != nil { 769 return nil, errors.Wrap(err, "failed to initialize uniswaprouter instance") 770 } 771 772 Logger.Info( 773 "Starting GetETHSwapAmount", 774 zap.Uint64("source", source)) 775 776 var result []*big.Int 777 778 result, err = uniswapRouterInstance.GetAmountsIn(&bind.CallOpts{From: from}, sourceInt, path) 779 if err != nil { 780 Logger.Error("GetAmountsIn FAILED", zap.Error(err)) 781 msg := "failed to execute GetAmountsIn call, ethereumAddress = %s" 782 783 return nil, errors.Wrapf(err, msg, from) 784 } 785 786 return result[0], nil 787 } 788 789 // SwapETH provides opportunity to perform zcn token swap operation using ETH as source token. 790 // - ctx go context instance to run the transaction 791 // - source source amount 792 // - target target amount 793 func (b *BridgeClient) SwapETH(ctx context.Context, source uint64, target uint64) (*types.Transaction, error) { 794 // 1. Swap source amount parameter. 795 sourceInt := big.NewInt(int64(source)) 796 797 // 2. Swap target amount parameter. 798 targetInt := big.NewInt(int64(target)) 799 800 uniswapNetworkInstance, transactOpts, err := b.prepareUniswapNetwork( 801 ctx, sourceInt, "swapETHForZCNExactAmountOut", targetInt) 802 if err != nil { 803 return nil, errors.Wrap(err, "failed to prepare uniswapnetwork") 804 } 805 806 Logger.Info( 807 "Starting SwapETH", 808 zap.Uint64("source", source), 809 zap.Uint64("target", target)) 810 811 var tran *types.Transaction 812 813 tran, err = uniswapNetworkInstance.SwapETHForZCNExactAmountOut(transactOpts, targetInt) 814 if err != nil { 815 return nil, errors.Wrap(err, "failed to execute swapETHForZCNExactAmountOut transaction") 816 } 817 818 return tran, nil 819 } 820 821 // SwapUSDC provides opportunity to perform zcn token swap operation using USDC as source token. 822 // - ctx go context instance to run the transaction 823 // - source source amount 824 // - target target amount 825 func (b *BridgeClient) SwapUSDC(ctx context.Context, source uint64, target uint64) (*types.Transaction, error) { 826 // 1. Swap target amount parameter. 827 sourceInt := big.NewInt(int64(source)) 828 829 // 2. Swap source amount parameter. 830 targetInt := big.NewInt(int64(target)) 831 832 uniswapNetworkInstance, transactOpts, err := b.prepareUniswapNetwork( 833 ctx, big.NewInt(0), "swapUSDCForZCNExactAmountOut", targetInt, sourceInt) 834 if err != nil { 835 return nil, errors.Wrap(err, "failed to prepare uniswapnetwork") 836 } 837 838 Logger.Info( 839 "Starting SwapUSDC", 840 zap.Uint64("source", source), 841 zap.Uint64("target", target)) 842 843 var tran *types.Transaction 844 845 tran, err = uniswapNetworkInstance.SwapUSDCForZCNExactAmountOut(transactOpts, targetInt, sourceInt) 846 if err != nil { 847 return nil, errors.Wrap(err, "failed to execute swapUSDCForZCNExactAmountOut transaction") 848 } 849 850 return tran, nil 851 } 852 853 // prepareUniswapNetwork performs uniswap network smart contract preparation actions. 854 func (b *BridgeClient) prepareUniswapNetwork(ctx context.Context, value *big.Int, method string, params ...interface{}) (*uniswapnetwork.Uniswap, *bind.TransactOpts, error) { 855 // 1. Uniswap smart contract address 856 contractAddress := common.HexToAddress(b.UniswapAddress) 857 858 // 2. To address parameter. 859 to := common.HexToAddress(b.TokenAddress) 860 861 // 3. From address parameter. 862 from := common.HexToAddress(b.EthereumAddress) 863 864 abi, err := uniswapnetwork.UniswapMetaData.GetAbi() 865 if err != nil { 866 return nil, nil, errors.Wrap(err, "failed to get uniswaprouter abi") 867 } 868 869 var pack []byte 870 871 pack, err = abi.Pack(method, params...) 872 if err != nil { 873 return nil, nil, errors.Wrap(err, "failed to pack arguments") 874 } 875 876 opts := eth.CallMsg{ 877 To: &to, 878 From: from, 879 Data: pack, 880 } 881 882 if value.Int64() != 0 { 883 opts.Value = value 884 } 885 886 transactOpts := b.CreateSignedTransactionFromKeyStore(b.ethereumClient, 0) 887 if value.Int64() != 0 { 888 transactOpts.Value = value 889 } 890 891 var uniswapNetworkInstance *uniswapnetwork.Uniswap 892 893 uniswapNetworkInstance, err = uniswapnetwork.NewUniswap(contractAddress, b.ethereumClient) 894 if err != nil { 895 return nil, nil, errors.Wrap(err, "failed to initialize uniswapnetwork instance") 896 } 897 898 return uniswapNetworkInstance, transactOpts, nil 899 } 900 901 func (b *BridgeClient) prepareToken(ctx context.Context, method string, tokenAddress common.Address, params ...interface{}) (*zcntoken.Token, *bind.TransactOpts, error) { 902 abi, err := zcntoken.TokenMetaData.GetAbi() 903 if err != nil { 904 return nil, nil, errors.Wrap(err, "failed to get zcntoken abi") 905 } 906 907 pack, err := abi.Pack(method, params...) 908 if err != nil { 909 return nil, nil, errors.Wrap(err, "failed to pack arguments") 910 } 911 912 from := common.HexToAddress(b.EthereumAddress) 913 914 gasLimitUnits, err := b.ethereumClient.EstimateGas(ctx, eth.CallMsg{ 915 To: &tokenAddress, 916 From: from, 917 Data: pack, 918 }) 919 if err != nil { 920 return nil, nil, errors.Wrap(err, "failed to estimate gas limit") 921 } 922 923 gasLimitUnits = addPercents(gasLimitUnits, 10).Uint64() 924 925 transactOpts := b.CreateSignedTransactionFromKeyStore(b.ethereumClient, gasLimitUnits) 926 927 var tokenInstance *zcntoken.Token 928 929 tokenInstance, err = zcntoken.NewToken(tokenAddress, b.ethereumClient) 930 if err != nil { 931 return nil, nil, errors.Wrap(err, "failed to initialize zcntoken instance") 932 } 933 934 return tokenInstance, transactOpts, nil 935 } 936 937 func (b *BridgeClient) prepareAuthorizers(ctx context.Context, method string, params ...interface{}) (*authorizers.Authorizers, *bind.TransactOpts, error) { 938 // To (contract) 939 contractAddress := common.HexToAddress(b.AuthorizersAddress) 940 941 // Get ABI of the contract 942 abi, err := authorizers.AuthorizersMetaData.GetAbi() 943 if err != nil { 944 return nil, nil, errors.Wrap(err, "failed to get ABI") 945 } 946 947 // Pack the method argument 948 pack, err := abi.Pack(method, params...) 949 if err != nil { 950 return nil, nil, errors.Wrap(err, "failed to pack arguments") 951 } 952 953 from := common.HexToAddress(b.EthereumAddress) 954 955 // Gas limits in units 956 gasLimitUnits, err := b.ethereumClient.EstimateGas(ctx, eth.CallMsg{ 957 To: &contractAddress, 958 From: from, 959 Data: pack, 960 }) 961 if err != nil { 962 return nil, nil, errors.Wrap(err, "failed to estimate gas") 963 } 964 965 // Update gas limits + 10% 966 gasLimitUnits = addPercents(gasLimitUnits, 10).Uint64() 967 968 transactOpts := b.CreateSignedTransactionFromKeyStore(b.ethereumClient, gasLimitUnits) 969 970 // Authorizers instance 971 authorizersInstance, err := authorizers.NewAuthorizers(contractAddress, b.ethereumClient) 972 if err != nil { 973 return nil, nil, errors.Wrap(err, "failed to create authorizers instance") 974 } 975 976 return authorizersInstance, transactOpts, nil 977 } 978 979 func (b *BridgeClient) prepareBridge(ctx context.Context, ethereumAddress, method string, params ...interface{}) (*bridge.Bridge, *bind.TransactOpts, error) { 980 // To (contract) 981 contractAddress := common.HexToAddress(b.BridgeAddress) 982 983 //Get ABI of the contract 984 abi, err := bridge.BridgeMetaData.GetAbi() 985 if err != nil { 986 return nil, nil, errors.Wrap(err, "failed to get ABI") 987 } 988 989 //Pack the method argument 990 pack, err := abi.Pack(method, params...) 991 if err != nil { 992 return nil, nil, errors.Wrap(err, "failed to pack arguments") 993 } 994 995 //Gas limits in units 996 fromAddress := common.HexToAddress(ethereumAddress) 997 998 gasLimitUnits, err := b.ethereumClient.EstimateGas(ctx, eth.CallMsg{ 999 To: &contractAddress, 1000 From: fromAddress, 1001 Data: pack, 1002 }) 1003 if err != nil { 1004 return nil, nil, errors.Wrap(err, "failed to estimate gas") 1005 } 1006 1007 //Update gas limits + 10% 1008 gasLimitUnits = addPercents(gasLimitUnits, 10).Uint64() 1009 1010 transactOpts := b.CreateSignedTransactionFromKeyStore(b.ethereumClient, gasLimitUnits) 1011 1012 // BridgeClient instance 1013 bridgeInstance, err := bridge.NewBridge(contractAddress, b.ethereumClient) 1014 if err != nil { 1015 return nil, nil, errors.Wrap(err, "failed to create bridge instance") 1016 } 1017 1018 return bridgeInstance, transactOpts, nil 1019 } 1020 1021 // getProviderType validates the provider url and exposes pre-defined type definition. 1022 func (b *BridgeClient) getProviderType() int { 1023 if strings.Contains(b.EthereumNodeURL, "g.alchemy.com") { 1024 return AlchemyProvider 1025 } else if strings.Contains(b.EthereumNodeURL, "rpc.tenderly.co") { 1026 return TenderlyProvider 1027 } else { 1028 return UnknownProvider 1029 } 1030 } 1031 1032 // estimateTenderlyGasAmount performs gas amount estimation for the given transaction using Tenderly provider. 1033 func (b *BridgeClient) estimateTenderlyGasAmount(ctx context.Context, from, to string, value int64) (float64, error) { 1034 return 8000000, nil 1035 } 1036 1037 // estimateAlchemyGasAmount performs gas amount estimation for the given transaction using Alchemy provider 1038 func (b *BridgeClient) estimateAlchemyGasAmount(ctx context.Context, from, to, data string, value int64) (float64, error) { 1039 client := jsonrpc.NewClient(b.EthereumNodeURL) 1040 1041 valueHex := ConvertIntToHex(value) 1042 1043 resp, err := client.Call(ctx, "eth_estimateGas", &AlchemyGasEstimationRequest{ 1044 From: from, 1045 To: to, 1046 Value: valueHex, 1047 Data: data}) 1048 if err != nil { 1049 return 0, errors.Wrap(err, "gas price estimation failed") 1050 } 1051 1052 if resp.Error != nil { 1053 return 0, errors.Wrap(errors.New(resp.Error.Error()), "gas price estimation failed") 1054 } 1055 1056 gasAmountRaw, ok := resp.Result.(string) 1057 if !ok { 1058 return 0, errors.New("failed to parse gas amount") 1059 } 1060 1061 gasAmountInt := new(big.Float) 1062 gasAmountInt.SetString(gasAmountRaw) 1063 1064 gasAmountFloat, _ := gasAmountInt.Float64() 1065 1066 return gasAmountFloat, nil 1067 } 1068 1069 // EstimateBurnWZCNGasAmount performs gas amount estimation for the given wzcn burn transaction. 1070 // - ctx go context instance to run the transaction 1071 // - from source address 1072 // - to target address 1073 // - amountTokens amount of tokens to burn 1074 func (b *BridgeClient) EstimateBurnWZCNGasAmount(ctx context.Context, from, to, amountTokens string) (float64, error) { 1075 switch b.getProviderType() { 1076 case AlchemyProvider: 1077 abi, err := bridge.BridgeMetaData.GetAbi() 1078 if err != nil { 1079 return 0, errors.Wrap(err, "failed to get ABI") 1080 } 1081 1082 clientID := DefaultClientIDEncoder(zcncore.GetClientWalletID()) 1083 1084 amount := new(big.Int) 1085 amount.SetString(amountTokens, 10) 1086 1087 var packRaw []byte 1088 packRaw, err = abi.Pack("burn", amount, clientID) 1089 if err != nil { 1090 return 0, errors.Wrap(err, "failed to pack arguments") 1091 } 1092 1093 pack := "0x" + hex.EncodeToString(packRaw) 1094 1095 return b.estimateAlchemyGasAmount(ctx, from, to, pack, 0) 1096 case TenderlyProvider: 1097 return b.estimateTenderlyGasAmount(ctx, from, to, 0) 1098 } 1099 1100 return 0, errors.New("used json-rpc does not allow to estimate gas amount") 1101 } 1102 1103 // EstimateMintWZCNGasAmount performs gas amount estimation for the given wzcn mint transaction. 1104 // - ctx go context instance to run the transaction 1105 // - from source address 1106 // - to target address 1107 // - zcnTransactionRaw zcn transaction (hashed) 1108 // - amountToken amount of tokens to mint 1109 // - nonceRaw nonce 1110 // - signaturesRaw authorizer signatures 1111 func (b *BridgeClient) EstimateMintWZCNGasAmount( 1112 ctx context.Context, from, to, zcnTransactionRaw, amountToken string, nonceRaw int64, signaturesRaw [][]byte) (float64, error) { 1113 switch b.getProviderType() { 1114 case AlchemyProvider: 1115 amount := new(big.Int) 1116 amount.SetString(amountToken, 10) 1117 1118 zcnTransaction := DefaultClientIDEncoder(zcnTransactionRaw) 1119 1120 nonce := new(big.Int) 1121 nonce.SetInt64(nonceRaw) 1122 1123 fromRaw := common.HexToAddress(from) 1124 1125 abi, err := bridge.BridgeMetaData.GetAbi() 1126 if err != nil { 1127 return 0, errors.Wrap(err, "failed to get ABI") 1128 } 1129 1130 var packRaw []byte 1131 packRaw, err = abi.Pack("mint", fromRaw, amount, zcnTransaction, nonce, signaturesRaw) 1132 if err != nil { 1133 return 0, errors.Wrap(err, "failed to pack arguments") 1134 } 1135 1136 pack := "0x" + hex.EncodeToString(packRaw) 1137 1138 return b.estimateAlchemyGasAmount(ctx, from, to, pack, 0) 1139 case TenderlyProvider: 1140 return b.estimateTenderlyGasAmount(ctx, from, to, 0) 1141 } 1142 1143 return 0, errors.New("used json-rpc does not allow to estimate gas amount") 1144 } 1145 1146 // estimateTenderlyGasPrice performs gas estimation for the given transaction using Tenderly API. 1147 func (b *BridgeClient) estimateTenderlyGasPrice(ctx context.Context) (float64, error) { 1148 return 1, nil 1149 } 1150 1151 // estimateAlchemyGasPrice performs gas estimation for the given transaction using Alchemy enhanced API returning 1152 // approximate final gas fee. 1153 func (b *BridgeClient) estimateAlchemyGasPrice(ctx context.Context) (float64, error) { 1154 client := jsonrpc.NewClient(b.EthereumNodeURL) 1155 1156 resp, err := client.Call(ctx, "eth_gasPrice") 1157 if err != nil { 1158 return 0, errors.Wrap(err, "gas price estimation failed") 1159 } 1160 1161 if resp.Error != nil { 1162 return 0, errors.Wrap(errors.New(resp.Error.Error()), "gas price estimation failed") 1163 } 1164 1165 gasPriceRaw, ok := resp.Result.(string) 1166 if !ok { 1167 return 0, errors.New("failed to parse gas price") 1168 } 1169 1170 gasPriceInt := new(big.Float) 1171 gasPriceInt.SetString(gasPriceRaw) 1172 1173 gasPriceFloat, _ := gasPriceInt.Float64() 1174 1175 return gasPriceFloat, nil 1176 } 1177 1178 // EstimateGasPrice performs gas estimation for the given transaction. 1179 // - ctx go context instance to run the transaction 1180 func (b *BridgeClient) EstimateGasPrice(ctx context.Context) (float64, error) { 1181 switch b.getProviderType() { 1182 case AlchemyProvider: 1183 return b.estimateAlchemyGasPrice(ctx) 1184 case TenderlyProvider: 1185 return b.estimateTenderlyGasPrice(ctx) 1186 } 1187 1188 return 0, errors.New("used json-rpc does not allow to estimate gas price") 1189 }