Anchor与Scatter一起用

参考案例

一、登录时选择钱包

  1. 把选择的钱包写入本地存储,最好写入sessionStorage中,这样页面关闭时就不存在了,下次页面重新打开需要重新登录。 注意:如果是APLink访问,则不需要选择Anchor钱包,毕竟没办法扫码自己。

export const isAPLink = window.navigator.userAgent
  .toLowerCase()
  .includes('aplink');

如果是APlink访问,直接选择第一个钱包,即scatter,否则弹出钱包选择框,让用户自己选择钱包。

         <div
            className={styles.btn}
            onClick={() => isAPLink ? selectWallet(walletList[0]) : setShowWalletList(true)}
          >
            {intl.get('connectWallet')}
          </div>
  1. 如果是anchor登录,需要把link实例存放好,因为无关页面渲染,所以在NFTone中直接存入全局变量中。

export function initLink() {
  if (!window.__LINK__) {
    const transport = new AnchorLinkBrowserTransport();
    const link = new AnchorLink({
      transport,
      service: "https://fwd.aplink.app", // 'ws://192.168.80.152:7001', // 'http://fwd.aplink.app', //
      chains: [
        {
          chainId: network.chainId,
          nodeUrl: `${network.protocol}://${network.host}`,
        },
      ],
    });
    window.__LINK__ = link;
  }
  return window.__LINK__;
}

// 登录
export async function login(dispatch) {
  const link = initLink();
  const identity = await link.login(scope);
  // ...
}

二、具体交易的发起

  • 写个工具函数实现不同钱包发起交易不同

import { scope } from './anchor';
import { getClient } from './client';
import { WALLET } from './const';
import storage from './storage';


export type action = {
  contract: string; // 合约名称
  name: string; // 合约方法名
  data: any; // 合约参数
};
/**
 * 调用合约
 */
export async function transact(actions: action[]) {
  // 过滤出正确的action
  actions = actions.filter(item => checkAction(item));


  if (!Array.isArray(actions) || !actions.length) {
    return;
  }


  const wallet = storage.get("wallet");


  try {
    if (wallet === WALLET.ANCHOR) {
      return await anchor(actions);
    } else if (wallet === WALLET.SCATTER) {
      return await scatter(actions);
    }
  } catch (e) {
    const error: any = typeof e === 'string' ? JSON.parse(e) : e;
    if (error?.error) {
      const { code, details = [] } = error.error;
      if (code === 3050003) {
        // 断言错误
        const res = details[0]?.message?.match(/\$\$\$(\d+)\$\$\$/);
        if (res) {
          message.error(
            `$$$${res[1]}$$$: ${intl.get(`contract_error_${res[1]}`)}`,
          );
        } else {
          message.error(`${code}: ${details[0]?.message}`);
        }
      } else {
        message.error(`${code}: ${intl.get(code)}`);
      }
    }
    throw e;
  }
}


function checkAction(action: action) {
  const { contract, data, name } = action;
  if (!contract || !data || !name) {
    return false;
  }
  return true;
}


/**
 * Scatter发起交易
 * @param actions
 * @returns
 */
async function scatter(actions: action[]) {
  const client = await getClient();
  if (actions.length === 1) {
    const { contract, data, name } = actions[0];
    const contractObj = await client.contract(contract);
    return await contractObj[name](data);
  } else {
    return await client.transaction(
      actions.map(action => action.contract),
      obj => {
        for (const action of actions) {
          obj[action.contract.replace(".", "_")][action.name](action.data);
        }
      },
    );
  }
}


/**
 * Anchor发起交易
 * @param actions
 * @returns
 */
async function anchor(actions: action[]) {
  const walletAddress = storage.get("walletAddress");
  const authority = storage.get("authority");


  // @ts-ignore
  const session = await window.__LINK__.restoreSession(scope);


  const toAction = (action: action) => ({
    account: action.contract,
    name: action.name,
    authorization: [
      {
        actor: walletAddress,
        permission: authority,
      },
    ],
    data: action.data,
  });


  if (actions.length === 1) {
    return await session.transact(
      { action: toAction(actions[0]) },
      { broadcast: true },
    );
  } else {
    return await session.transact(
      { actions: actions.map(item => toAction(item)) },
      { broadcast: true },
    );
  }
}
  • 然后我们就可以愉快的修改以前基于Scatter写的代码了。

const params = {
  from: walletAddress,
  to: detail.project_contract_address,
  quantity: asset.stringify({
    ...amaxCurrencyTokens[token.symbol],
    amount: totalPrice,
  }),
  memo: `booth:${detail.project_id}`,
};
const client = await getClient();
const contract = await client.contract(token.contract);
const res = await contract.transfer(params);

修改为

const params = {
  from: walletAddress,
  to: detail.project_contract_address,
  quantity: asset.stringify({
    ...amaxCurrencyTokens[token.symbol],
    amount: totalPrice,
  }),
  memo: `booth:${detail.project_id}`,
};

// 把需要的参数抽出来,推送交易由transact方法来完成,transact方法实现按不同钱包推送交易。
const res = await transact([{
  contract: token.contract,
  name: "transfer",
  data: params
}]);

是不是很简单

三、不依赖钱包的链上查询,则使用 '@amax/amaxjs来实现

import Amax from '@amax/amaxjs';


export function getLocalClient() {
  const client = Amax({
    httpEndpoint: `${network.protocol}://${network.host}`,
    chainId: network.chainId,
  });
  return client;
}


const client = await getLocalClient();
const id = Number(params.id);
try {
  return await client.getTableRows({
    code: contractName.pass,
    scope: contractName.pass,
    table: 'passes',
    json: true,
    limit: 1,
    index_position: 1,
    upper_bound: id,
    lower_bound: id,
  });
} catch (error) {
  console.log(error);
}

// ...或者其它操作

如何与后端进行登录验证?

export async function login(dispatch) {
  const link = initLink();
  const identity = await link.login(scope);
  const { account, proof, proofKey, proofValid } = await verifyProof(
    link,
    identity,
  );


  const walletAddress = proof.signer.actor.toString();
  const authority = proof.signer.permission.toString();
  const chainId = network.chainId;


  storage.set('walletAddress', walletAddress);
  storage.set('authority', authority);
  storage.set('chainId', chainId);

  // proof信息传给后端进行签名校验,成功返回token,失败则退出link登录。
  await dispatch({
    type: 'login/login',
    params: {
      signature: proof.signature.toString(),
      message: '',
      wallet_address: walletAddress,
      authority,
      scope: proof.scope.toString(),
      expiration: proof.expiration.toString(),
    },
    callback: async ({ data, code }) => {
      if (code === 200) {
        updateToken(data.token);
        await dispatch({
          type: 'global/updateState',
          state: {
            isLogin: true,
            walletAddress,
            chainId,
          },
        });
        setTimeout(eventBus.trigger, 400, 'getBalance');
      } else {
        await link.clearSessions(scope);
        storage.remove('walletAddress');
        storage.remove('authority');
        storage.remove('chainId');
      }
    },
  });
}

最后更新于