最近在写ETH的NFT发行转账功能,使用的语言是PHP,但是发现github上使用比较多的web3.php有点问题,当solidity使用string[]类型时候web3.php没有做兼容,最后会导致签名后的数据有问题,交易出现 Warning! Error encountered during contract execution [execution reverted] ,修改后特意来记录一下。

composer.php:

"require" : { "sc0vu/web3.php" : "dev-master" , "web3p/ethereum-tx" : "dev-master" , "simplito/elliptic-php" : "~1.0.4" , "kornrunner/keccak" : "~1.0" , "graze/guzzle-jsonrpc" : "^3.2" , "bitwasp/buffertools" : "^0.5.0"

IPFSapi和ETHapi都是使用的infura:

https://infura.io/

php上传文件到IPFS可以参考前面的文章,ETH的原生签名交易也可以参考前面的文章。

web3实现ETH ERC20、ERC721签名时候data数据的拼装,封装了一个类可以参考一下:

* Created by PhpStorm. * User: Echo * Date: 2021/8/24 * Time: 5:07 PM use EthTool\\Credential ; use EthTool\\EthInfuraApi ; use Web3\\Contract ; use Web3\\Utils ; use EthTool\\EthApi ; use Web3\\Contracts\\Ethabi ; use Web3\\Contracts\\Types\\Address ; use Web3\\Contracts\\Types\\Boolean ; use Web3\\Contracts\\Types\\Bytes ; use Web3\\Contracts\\Types\\DynamicBytes ; use Web3\\Contracts\\Types\\Integer ; use Web3\\Contracts\\Types\\Str ; use Web3\\Contracts\\Types\\Uinteger ; use IPFS \\ IPFS ; use Web3\\Web3 ; class Nft { private $abi = '你的ABI' ; //abi private $contract_address = '' ; private $key = "" ; private $self_address = "" ; private $credential ; private $eth_host = "" ; private $api_key = "" ; private $ethabi ; private $eth_api ; private $address_key = "address:nonce:key:" ; * Nft constructor. public function __construct ( ) { $eth_config = config ( "myconfig.ETH" ) ; $this -> contract_address = $eth_config [ "contract_address" ] ; $this -> key = $eth_config [ "my_key" ] ; $this -> credential = Credential :: fromKey ( $this -> key ) ; $this -> self_address = $this -> credential -> getAddress ( ) ; $this -> eth_host = $eth_config [ "api_host" ] ; $this -> api_key = $eth_config [ "api_key" ] ; $this -> eth_api = new EthInfuraApi ( $this -> eth_host , $this -> api_key ) ; $this -> ethabi = new Ethabi ( [ 'address' => new Address , 'bool' => new Boolean , 'bytes' => new Bytes , 'dynamicBytes' => new DynamicBytes , 'int' => new Integer , 'string' => new Str , 'uint' => new Uinteger , ] ) ; * 发行NFT * @param $name string NFT名称 * @param $description string NFT介绍 * @param $img_url string NFT图片在阿里云的链接 * @param int $number 要发行的个数 * @return array|bool|int|mixed|null|string * status: -1为失败 1成功 * ipfs_img_url_hash: 图片上传到IPFS的hash * ipfs_nft_info_url_hash: NFT信息上传到IPFS的hash public function createNft ( $name , $description , $img_url , $number = 1 ) { $number = ( int ) $number ; if ( ! $name || ! $description || ! $img_url || $number < 1 ) { return [ "status" => - 1 , "msg" => "参数格式有误" $return_info = [ "name" => $name , "description" => $description , "img_url" => $img_url , "mint_hash" => "" , "nft_info" => [ ] $img_url_hash = $this -> uploadPhoto ( $img_url ) ; if ( is_array ( $img_url_hash ) && isset ( $img_url_hash [ "status" ] ) ) { return $img_url_hash ; $info_url = $this -> uploadData ( $img_url_hash , $name , $description ) ; if ( is_array ( $info_url ) && isset ( $info_url [ "status" ] ) ) { return $info_url ; $new_token_id = $this -> getNewTokenId ( $this -> self_address , $info_url ) ; if ( is_array ( $new_token_id ) && isset ( $new_token_id [ "status" ] ) ) { return $new_token_id ; if ( $number == 1 ) { $mint_hash = $this -> mintNft ( $this -> self_address , $info_url ) ; if ( is_array ( $mint_hash ) && isset ( $mint_hash [ "status" ] ) ) { return $mint_hash ; array_push ( $return_info [ "nft_info" ] , "ipfs_img_url_hash" => $img_url_hash , "ipfs_nft_info_url_hash" => $info_url , "nft_id" => $new_token_id } else { $address_array = [ ] ; $url_array = [ ] ; for ( $i = 1 ; $i <= $number ; $i ++ ) { $address_array [ ] = $this -> self_address ; $url_array [ ] = $info_url ; array_push ( $return_info [ "nft_info" ] , "ipfs_img_url_hash" => $img_url_hash , "ipfs_nft_info_url_hash" => $info_url , "nft_id" => $new_token_id $new_token_id += 1 ; $mint_hash = $this -> mintArrayNft ( $address_array , $url_array ) ; if ( is_array ( $mint_hash ) && isset ( $mint_hash [ "status" ] ) ) { return $mint_hash ; $return_info [ "mint_hash" ] = $mint_hash ; return [ "status" => 1 , "msg" => "成功" , "data" => $return_info try { $key = Credential :: newWallet ( ) ; $credential = Credential :: fromKey ( $key ) ; return $data = [ 'private' => $credential -> getPrivateKey ( ) , 'public' => $credential -> getPublicKey ( ) , 'address' => $credential -> getAddress ( ) } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) * 查询地址中NFT的数量 * @param $address * @return array|bool|mixed|string public function nftBalance ( $address ) { try { $param_data = $this -> ethabi -> encodeParameter ( 'address' , $address ) ; $param_data = Utils :: stripZero ( $param_data ) ; $method_id = $this -> ethabi -> encodeFunctionSignature ( "balanceOf(address)" ) ; $number = $this -> eth_api -> getCall ( $this -> self_address , $this -> contract_address , "0x0" , $method_id . $param_data ) ; // $number = Utils::stripZero($number); $number = Utils :: toBn ( $number ) -> toString ( ) ; return $number ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) * 根据tokenID返回持有者地址 * @param $id * @return array|bool|mixed|string public function getAddressByTokenId ( $id ) { try { $param_data = $this -> ethabi -> encodeParameter ( 'uint256' , $id ) ; $param_data = Utils :: stripZero ( $param_data ) ; $method_id = $this -> ethabi -> encodeFunctionSignature ( "ownerOf(uint256)" ) ; $address = $this -> eth_api -> getCall ( $this -> self_address , $this -> contract_address , "0x0" , $method_id . $param_data ) ; $address = $this -> ethabi -> decodeParameter ( 'address' , $address ) ; return $address ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) * 根据TokenID返回Token的URL信息 * @param $id * @return array|bool|mixed|string public function getUrlByTokenId ( $id ) { try { $param_data = $this -> ethabi -> encodeParameter ( 'uint256' , $id ) ; $param_data = Utils :: stripZero ( $param_data ) ; $method_id = $this -> ethabi -> encodeFunctionSignature ( "tokenURI(uint256)" ) ; $url = $this -> eth_api -> getCall ( $this -> self_address , $this -> contract_address , "0x0" , $method_id . $param_data ) ; $url = $this -> ethabi -> decodeParameter ( 'string' , $url ) ; return $url ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) * 返回最新的NFT的ID(预铸造、本地计数使用) * @param $to_address * @param $nft_url * @return array|bool|mixed|string public function getNewTokenId ( $to_address , $nft_url ) { try { $param_data = $this -> ethabi -> encodeParameters ( [ 'address' , 'string' ] , [ $to_address , "ipfs://" . $nft_url ] $param_data = Utils :: stripZero ( $param_data ) ; $method_id = $this -> ethabi -> encodeFunctionSignature ( "mint(address,string)" ) ; $number = $this -> eth_api -> getCall ( $this -> self_address , $this -> contract_address , "0x0" , $method_id . $param_data ) ; $number = Utils :: toBn ( $number ) -> toString ( ) ; return $number ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) * 铸造一个NFT * @param $to_address address 发布到的地址 * @param $nft_url string NFT的信息URL * @return array|bool|mixed public function mintNft ( $to_address , $nft_url ) { try { $param_data = $this -> ethabi -> encodeParameters ( [ 'address' , 'string' ] , [ $to_address , "ipfs://" . $nft_url ] $param_data = Utils :: stripZero ( $param_data ) ; $method_id = $this -> ethabi -> encodeFunctionSignature ( "mint(address,string)" ) ; $address_key = $this -> address_key . $this -> self_address ; getRedis ( ) -> del ( $address_key ) ; $nonce_num = getRedis ( ) -> get ( $address_key ) ; if ( ! $nonce_num ) { $nonce_num = $this -> getNonce ( $this -> self_address ) ; $nonce = Utils :: toHex ( $nonce_num , true ) ; $gas_limit = $this -> eth_api -> getEstimateGas ( $this -> self_address , $this -> contract_address , "0x0" , $method_id . $param_data ) ; $gasprice = $this -> eth_api -> getGasPrice ( ) ; $data = [ 'nonce' => $nonce , 'gasPrice' => $gasprice , 'gasLimit' => $gas_limit , //16进制 'to' => $this -> contract_address , //代币地址 'value' => '0x0' , //substr(Utils::sha3("mint(address,string)",true),0,10) == 0xd0def521 'data' => $method_id . $param_data , // 'chainId' => 80001, 'chainId' => 137 $signed = $this -> credential -> signTransaction ( $data ) ; // 进行离线签名 $hash = $this -> eth_api -> sendRawTransaction ( $signed ) ; getRedis ( ) -> setex ( $address_key , 43200 , $nonce_num + 1 ) ; return $hash ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) public function web3Test ( $address , $url ) { // $web3 = new Web3("https://rpc-mumbai.maticvigil.com/"); $web3 = new Web3 ( $this -> eth_host . $this -> api_key ) ; $contract = new Contract ( $web3 -> provider , $this -> abi ) ; $data_aaa = $contract -> at ( $this -> contract_address ) -> getData ( 'mintArray' , $address , $url ) ; return $data_aaa ; * @param $to_address array 发布到的地址 * @param $nft_url array NFT的信息URL * @return array|bool|mixed public function mintArrayNft ( $to_address , $nft_url ) { if ( count ( $to_address ) !== count ( $nft_url ) || count ( $to_address ) < 1 ) { return [ "status" => - 1 , "msg" => "地址数和NFT信息数不同" try { foreach ( $nft_url as & $one_url ) { $one_url = "ipfs://" . $one_url ; $param_data = $this -> ethabi -> encodeParameters ( [ 'address[]' , 'string[]' ] , [ $to_address , $nft_url ] $param_data = Utils :: stripZero ( $param_data ) ; // var_dump($param_data); // $web3_data = $this->web3Test($to_address,$nft_url); $method_id = $this -> ethabi -> encodeFunctionSignature ( "mintArray(address[],string[])" ) ; $address_key = $this -> address_key . $this -> self_address ; getRedis ( ) -> del ( $address_key ) ; $nonce_num = getRedis ( ) -> get ( $address_key ) ; if ( ! $nonce_num ) { $nonce_num = $this -> getNonce ( $this -> self_address ) ; $nonce = Utils :: toHex ( $nonce_num , true ) ; $gas_limit = $this -> eth_api -> getEstimateGas ( $this -> self_address , $this -> contract_address , "0x0" , $method_id . $param_data ) ; $gasprice = $this -> eth_api -> getGasPrice ( ) ; $data = [ 'nonce' => $nonce , // 'gasPrice' => '0x' . Utils::toWei("8", 'gwei')->toHex(), 'gasPrice' => $gasprice , 'gasLimit' => $gas_limit , //16进制 // 'gasLimit' => "0x61a80", //16进制 'to' => $this -> contract_address , //代币地址 'value' => '0x0' , //substr(Utils::sha3("mint(address,string)",true),0,10) == 0xd0def521 'data' => $method_id . $param_data , // 'chainId' => 80001, 'chainId' => 137 // var_dump($data); $signed = $this -> credential -> signTransaction ( $data ) ; // 进行离线签名 $hash = $this -> eth_api -> sendRawTransaction ( $signed ) ; getRedis ( ) -> setex ( $address_key , 43200 , $nonce_num + 1 ) ; return $hash ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) public function transferNft ( $to_address , $nft_id ) { //Todo public function transferMatic ( $to_address , $number ) { //Todo * 拿到地址交易的nonce值 * @param $address * @return bool|mixed|string * @throws \\Exception protected function getNonce ( $address ) { $nonce_num = $this -> eth_api -> getTransactionCount ( $address ) ; if ( $nonce_num == false ) { return false ; $nonce_num = Utils :: toBn ( $nonce_num ) -> toString ( ) ; return $nonce_num ; * 往IPFS上传图片 * @param $url string 图片的远程链接 * @return array|mixed|null public function uploadPhoto ( $url ) { try { $ipfs = new IPFS ( ) ; $hash = $ipfs -> addFromUrl ( $url ) ; // var_dump($ipfs->pinAdd($hash)); return $hash ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( ) * 根据图片的链接生成NFT介绍信息并上传到IPFS * @param $url string 图片在IPFS的链接 * @param $name string NFT名称 * @param $description string NFT介绍 * @return array|null|string public function uploadData ( $url , $name , $description ) { try { $ipfs = new IPFS ( ) ; $data = [ "name" => $name , "description" => $description , "image" => "ipfs://" . $url $hash = $ipfs -> add ( json_encode ( $data ) ) ; return $hash ; } catch ( \ \ Exception $e ) { return [ "status" => $e -> getCode ( ) , "msg" => $e -> getMessage ( )

web3.php有问题的代码主要为encodeParameters()方法,也就是对data数据需要的方法和参数进行转换时候。

修改的具体文件为: vendor/sc0vu/web3.php/src/Contracts/SolidityType.php encode 方法,下面为我修改后的代码:

* encode * @param mixed $value * @param string $name * @return string public function encode ( $value , $name ) if ( $this -> isDynamicArray ( $name ) ) { $length = count ( $value ) ; $nestedName = $this -> nestedName ( $name ) ; $result = [ ] ; $result [ ] = IntegerFormatter :: format ( $length ) ; if ( $this -> isDynamicType ( $nestedName ) ) { $start = 0 ; foreach ( $value as $k => $val ) { if ( $start == 0 ) { $l = $length * 32 ; } else { $v_1 = Utils :: toHex ( $value [ $k - 1 ] ) ; $l = ( floor ( ( mb_strlen ( $v_1 ) + 63 ) / 64 ) + 1 ) * 32 ; $start += $l ; $result [ ] = IntegerFormatter :: format ( $start ) ; //var_dump($result); // die(); foreach ( $value as $val ) { $result [ ] = $this -> encode ( $val , $nestedName ) ; return $result ; } elseif ( $this -> isStaticArray ( $name ) ) { $length = $this -> staticArrayLength ( $name ) ; $nestedName = $this -> nestedName ( $name ) ; $result = [ ] ; foreach ( $value as $val ) { $result [ ] = $this -> encode ( $val , $nestedName ) ; return $result ; return $this -> inputFormat ( $value , $name ) ;

之所以这么改是因为官方说string是动态元素,所以要加上偏移量,原话如下:

since strings are dynamic elements we need to find their offsets c , d and e :

参考链接: https://docs.soliditylang.org/en/latest/abi-spec.html#use-of-dynamic-types

ERC20 令牌水龙头 以太坊 主网上以及Ropsten,Kovan,Rinkeby和Görli测试网上的 ERC20 令牌龙头。 访问页面,设置适当的代币数量,单击“免费代币”按钮。 而已! 您将需要一些以太( ETH )来支付网络 交易 费用。 请参阅下面的,以获得testnet醚。 部署的水龙头令牌 Testnet乙醚龙头 Testnet ETH 龙头 请提交PR以及上述信息的更新。 (c)PepperSec.com/麻省理工学院执照。
EOSIO21协议:victory_hand_light_skin_tone::index_pointing_up_medium-light_skin_tone: 将您的 ERC20 令牌传送到EOS(或任何EOSIO侧链或分叉-例如WAX,TELOS或BOS)。 EOSIO21是启用跨链的协议 :chains: ETH 和EOS之间的代币移动。 ETH ERC20 )-> EOS21->任何EOSIO链(代币) 该协议的目的是为应用程序开发人员提供一种在链之间移动其令牌和应用程序的标准。 加入,讨论EOS21。 另外,请考虑对sheos21sheos作为Block Producer投票。 -EOS区块链 -EOS合同开发工具包 -Javascript运行时(已通过v8.10和10.11测试) EOSIO
Chainlink是一个去中心化的预言机网络,它可以让区块链中的智能合约安全地访问外部世界的数据。在这个教程中,我们将探索chainlink预言机网络的搭建,并学习如何使用预置或自定义的适配器 实现 智能合约与外部世界数据的桥接。 以太坊 教程链接: Dapp入门 | 电商Dapp实战 | ERC721 实战 | Php 对接 | Java 对接 | Python 对接 | C# 对接 | Dart 对接 智能合约被锁定在区块链里,与外部世界隔离开来。然而在许多应用中,智能合约的 运行需要依赖于外部真实世界的信息。 以Ocean协议为例:只有当提供的数据被证明是可以使用时,数据提供商才可以得到代币奖励。因此一个
composer require sc0vu/web3. php dev-master 或者您可以在composer.json中添加此行 "sc0vu/web3. php ": "dev-master" use Web3 \ Web3 ; $ web3 = new Web3 ( 'http://localhost:8545' ); 使用提供者 use Web3 \ Web3 ; use Web3 \ Providers \ 所有 实现 了这些函数的合约都是 ERC20 Token ERC20 可以表示任何同质的可以 交易 的内容: 货币、股票、积分、债券、利息... 可以用数量来表示的内容 基本上可以 ERC20 表示 ERC 20 的缺点 以下是一个遇到很多次的场景:有一天老板过来找你(开发者),最近存币生息很火,我们也做一个合约吧, 用户打币过来给他计算利息, 看起来是一个很简单的需求,你满口答应说好,结果自
linux中混杂设备的定义:在linux中存在一类字符设备,它们共享一个主设备号,但次设备号不同,这类字符设备被称为混杂设备。 linux中描述一个混杂设备 struct miscdevice { int minor; 次设备号 const char * name; 设备名 const struct file_operations *ops; struct head_list head; struct device *parent; struct device *this_module; 注册一个混杂设
好的。 ERC20 是一种常用的 以太坊 代币标准,用Web3.js来 实现 ERC20 授权,你需要先确保已经安装并配置了Web3.js。 一、首先,你需要获取 ERC20 合约的地址和ABI(应用程序二进制接口)。 二、然后,你可以使用Web3.js的` eth .contract`方法来创建一个合约对象。 const contract = new web3. eth .Contract(ABI, contractAddress); 三、接着,你可以调用合约对象的`approve`方法来 实现 授权。 contract.m eth ods.approve(spender, amount).send({ from: owner, gas: GAS_LIMIT }, function(error, transactionHash) { if (!error) { console.log(transactionHash); } else { console.error(error); 上面的代码中,`spender`是你要授权的地址,`amount`是你要授权的数量,`owner`是你的地址,`GAS_LIMIT`是执行 交易 所需的最大gas数量。 注意:需要确保你在调用`approve`方法时已经连接到 以太坊 网络,并且你有足够的以太币来支付执行 交易 的gas费用。