ryan

Dapp如何与线上只能合约通信

35 次阅读

在web2开发中,前端与后端通信大多通过HTTP的方式,比如说用的最多的Axios.js, 是一个基于 Promise 的 HTTP 客户端库,主要用于在浏览器和 Node.js 环境中发送 HTTP 请求.但在 DApp(去中心化应用)中,通信方式发生了本质性的变化。

DApp 前端如何与合约通信?

我们以 ethers.js 为例,介绍通信流程。

步骤一,获取用户钱包授权


const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send("eth_requestAccounts", []); // 请求连接钱包
const signer = await provider.getSigner();      // 获取用户签名器(用于发交易)

步骤二,加载合约实例


const contractAddress = '0xYourContractAddress';
const contractABI = [...] // 从部署后的合约 artifacts 中获取

const contract = new ethers.Contract(contractAddress, contractABI, signer);

步骤三:调用合约的读取方法(不会产生交易)


const name = await contract.name(); // 读取合约中的状态,不上链
console.log("Token Name:", name);

步骤四:调用合约的写入方法(会生成交易)


const tx = await contract.mint("0xRecipientAddress", 1); // 写操作,上链 await tx.wait(); // 等待交易打包确认 console.log("NFT minted");

接下来介绍一下ethers.js与合约通信的的源码解析,首先看Contract类https://github.com/ethers-io/ethers.js/blob/v6/lib.esm/contract/contract.js


export class BaseContract {
	   constructor(target, abi, runner, _deployTx) {
			.....
		    return new Proxy(this, {
            get: (target, prop, receiver) => {
 
             	.....	
                return target.getFunction(prop);

        });
	}
}

实例化一个合约,需要传入合约地址、abi以及singer,当一个合约实例执行合约的方法的时候,从源码可以看出实际执行的是 target.getFunction(prop),abi的处理在之前的文章有介绍过.

   getFunction(key) {
        if (typeof (key) !== "string") {
            key = key.format();
        }
        const func = buildWrappedMethod(this, key);
        return func;
    }

执行buildWrappedMethod时,会根据fragment的constant来判断具体执行哪个function如果constant为true(即执行合约的function是view或者view),则执行staticCall


     const fragment = getFragment(...args);
        if (fragment.constant) {
            return await staticCall(...args);
        }
        return await send(...args);

我们来看看staticCall的具体逻辑,


 const staticCallResult = async function (...args) {
        const runner = getRunner(contract.runner, "call");
        assert(canCall(runner), "contract runner does not support calling", "UNSUPPORTED_OPERATION", { operation: "call" });
		// tx的值为
		// {
		// data: "0x22f4596f",
		// to:""0x5FbDB2315678afecb367f032d93F642f64180aa3""
		//	}
        const tx = await populateTransaction(...args);
        let result = "0x";
        try {
			// 执行合约的方法
			// 在 ethers/src.ts/providers/abstract-provider.ts 文件中
			// 它最终会发送一个 eth_call JSON-RPC
            result = await runner.call(tx);
        }
        catch (error) {
            if (isCallException(error) && error.data) {
                throw contract.interface.makeError(error.data, tx);
            }
            throw error;
        }
        const fragment = getFragment(...args);
// 这里执行的是this.#abiCoder.decode(fragment.outputs, bytes);
        return contract.interface.decodeFunctionResult(fragment, result);
    };

最终是通过runner.call来发送JSON-RPC,runner就是provider

发表评论