sui
Dapp 开发者集成OpenBlock内嵌dappsdk,OpenBlock内部通过iframe加载Dapp url,Dapp使用OpenBlock Wallet Provider(window.obsui) OpenBlock钱包交互通信
提示
- Dapp使用全局变量window.obsui 为钱包provider 
- OpenBlock会在DApp加载完毕之后给Dapp发送一条消息,Dapp可以根据此消息来判断宿主应用是否为OpenBlock再加载innerdappsdk: 
// openblock side:
dappPageIframe.onload = () => {
  dappPageIframe.contentWindow?.postMessage(
    {
      from: 'openblock-content-integration',
      target: 'dapp-inpage-integration',
      value: 'dapploaded',
    },
    '*',
  );
};
// dapp side:
window.addEventListener(
  'message',
  (event) => {
    if (event.data.target === 'dapp-inpage-integration' && event.data.from === 'openblock-content-integration') {
      // fom openblock wallet
      return;
    }
  },
  false,
);
Wallet Provider Methods
Connect
const provider = window.obsui;
response: {
    accounts: ReadonlyWalletAccount[];
}
await provider.features['standard:connect'].connect();
Listen
const provider = window.obsui;
request: ...[event: string, listener: StandardEventsListener]
response: () => void;
await provider.features['standard:events'].on();
Execute Transaction
const provider = window.obsui;
request: SuiSignAndExecuteTransactionBlockInput {
    transactionBlock: TransactionBlock;
}
response: SuiSignAndExecuteTransactionBlockOutput {
    digest: TransactionDigest;
    effects: TransactionEffects;
}
await provider.features['sui:signAndExecuteTransactionBlock'].signAndExecuteTransactionBlock();
Sign Message
const provider = window.obsui;
request: SuiSignMessageInput {
    message: Uint8Array;
}
response: SuiSignMessageOutput {
    signature: string;
}
await provider.features['sui:signMessage'].signMessage();
Source code
inner-dappsdk.js
import mitt from 'mitt';
import { TransactionBlock, toB64 } from '@mysten/sui.js';
import {
  SUI_CHAINS,
  SUI_DEVNET_CHAIN,
  SUI_TESTNET_CHAIN,
  SUI_LOCALNET_CHAIN,
  ReadonlyWalletAccount,
} from '@mysten/wallet-standard';
const DAPP_INPAGE = 'dapp-inpage-integration';
const OPENBLOCK_CONTENT = 'openblock-content-integration';
const guid = () => {
  function S4() {
    return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
  }
  return S4() + S4() + S4() + S4() + S4() + S4() + S4() + S4();
};
const API_ENV = {
  local: 'local',
  devNet: 'devNet',
  testNet: 'testNet',
  customRPC: 'customRPC',
};
const API_ENV_TO_CHAIN = {
  [API_ENV.local]: SUI_LOCALNET_CHAIN,
  [API_ENV.devNet]: SUI_DEVNET_CHAIN,
  [API_ENV.testNet]: SUI_TESTNET_CHAIN,
};
class SuiProvider {
  constructor() {
    this._events = mitt();
    this._accounts = [];
    this._activeChain = null;
    this.callBackList = {};
    this.eventListener = {};
    this.isOpenBlock = () => true;
    this.hasPermissions = this.hasPermissions.bind(this);
    this.requestPermissions = this.requestPermissions.bind(this);
    this.getAccounts = this.getAccounts.bind(this);
    this._signAndExecuteTransactionBlock = this._signAndExecuteTransactionBlock.bind(this);
    this._signTransactionBlock = this._signTransactionBlock.bind(this);
    this._stake = this._stake.bind(this);
    this._signMessage = this._signMessage.bind(this);
    this._connected = this._connected.bind(this);
    this._connect = this._connect.bind(this);
    this._setAccounts = this._setAccounts.bind(this);
    this._setActiveChain = this._setActiveChain.bind(this);
  }
  get version() {
    return '1.0.0';
  }
  get name() {
    return 'OpenBlock';
  }
  get icon() {
    return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTUiIGhlaWdodD0iNTUiIHZpZXdCb3g9IjAgMCA1NSA1NSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjU1IiBoZWlnaHQ9IjU1IiBmaWxsPSIjRjlGQkZGIi8+CjxwYXRoIGQ9Ik0zNi44MTQ3IDEwLjc3NTFDNDMuNTQ0NSAxMC43NzUxIDQ5IDE2LjIzMDYgNDkgMjIuOTYwNFYyMy4yNDc2TDQ0Ljc0NDIgMjcuNUw0OSAzMS43NDgzVjMyLjAzOTZDNDkgMzguNzY5NCA0My41Mzk5IDQ0LjIyNDkgMzYuODEwMiA0NC4yMjQ5SDUuOTg4NTNMMTUuODQ0NSAzNy4xMTAzTDMyLjI3NTEgMzcuMjMwN0MzNy41NTMzIDM3LjIz MDcgNDEuOTAzMSAzMi43NzgyIDQxLjkwMzEgMjcuNUM0MS45MDMxIDIyLjIyMTggMzcuNTUzMyAxNy43NjEgMzIuMjc1MSAxNy43NjFIMTUuODQ0NUw1Ljk5MzA4IDEwLjc3NTFIMzYuODE0N1oiIGZpbGw9IiM0QTNERTYiLz4KPHBhdGggZD0iTTE4LjEzODQgMjMuNDM4MkMxNS44OTUyIDIzLjQzODIgMTQuMDc2NyAyNS4yNTY3IDE0LjA3NjcgMjcuNDk5OUMxNC4wNzY3IDI5Ljc0MzIgMTUuODk1MiAzMS41NjE3IDE4LjEzODQgMzEuNTYxN0gzMi4yODkyQzM0LjUzMjUgMzEuNTYxNyAzNi4zNTEgMjkuNzQzMiAzNi4zNTEgMjcuNDk5OUMzNi4zNTEgMjUuMjU2NyAzNC41MzI1IDIzLjQzODIgMzIuMjg5MiAyMy40MzgySDE4LjEzODRaIiBmaWxsPSIjNEEzREU2Ii8+Cjwvc3ZnPgo=';
  }
  get chains() {
    return SUI_CHAINS;
  }
  async _connected() {
    console.log('suiProvider::_connected');
    this._setActiveChain(this._getActiveNetwork());
    if (!(await this.hasPermissions(['viewAccount']))) {
      return;
    }
    const accounts = await this.getAccounts();
    this._setAccounts(accounts);
    if (this._accounts.length) {
      this._events.emit('change', { accounts: this.accounts });
    }
  }
  async _connect(input) {
    console.log('suiProvider::_connect, input:', input);
    await this._connected();
    if (!this._accounts.length) {
      await this.requestPermissions();
      await this._connected();
    }
    return { accounts: this.accounts };
  }
  get features() {
    return {
      'standard:connect': {
        version: '1.0.0',
        connect: this._connect,
      },
      'standard:events': {
        version: '1.0.0',
        on: (event, listener) => {
          console.log('suiProvider::standard:events, event:', event, 'listener:', listener);
          this._events.on(event, listener);
          return () => this._events.off(event, listener);
        },
      },
      'sui:signTransactionBlock': {
        version: '1.1.0',
        signTransactionBlock: this._signTransactionBlock,
      },
      'sui:signAndExecuteTransactionBlock': {
        version: '1.0.0',
        signAndExecuteTransactionBlock: this._signAndExecuteTransactionBlock,
      },
      'suiWallet:stake': {
        version: '0.0.1',
        stake: this._stake,
      },
      'sui:signMessage': {
        version: '1.0.0',
        signMessage: this._signMessage,
      },
    };
  }
  get accounts() {
    return this._accounts;
  }
  async hasPermissions(permissions) {
    return this._hookRequest({
      method: 'sui_accounts',
      params: permissions,
    }).then((res) => {
      return Promise.resolve(!!res);
    });
  }
  async requestPermissions(permissions) {
    return this._hookRequest({
      method: 'sui_requestAccounts',
      params: permissions,
    }).then((res) => {
      return Promise.resolve(!!res);
    });
  }
  async getAccounts() {
    return this._hookRequest({
      method: 'sui_accounts',
    });
  }
  async _signTransactionBlock(input) {
    console.log('suiProvider::_signTransactionBlock, input:', input);
    if (!TransactionBlock.is(input.transactionBlock)) {
      throw new Error('Unexpect transaction format found. Ensure that you are using the `Transaction` class');
    }
    return this._hookRequest({
      method: 'sui_signTransaction',
      params: {
        ...input,
        type: 'transaction',
        account: input.account?.address || this._accounts[0]?.address || '',
        data: input.transactionBlock.serialize(),
      },
    });
  }
  async _signAndExecuteTransactionBlock(input) {
    console.log('suiProvider::_signAndExecuteTransactionBlock, input:', input);
    if (!TransactionBlock.is(input.transactionBlock)) {
      throw new Error('Unexpect transaction format found. Ensure that you are using the `Transaction` class');
    }
    const result = await this._hookRequest({
      method: 'sui_signAndExecuteTransaction',
      params: {
        type: 'transaction',
        data: input.transactionBlock.serialize(),
        options: input.options,
        account: input.account?.address || this._accounts[0]?.address || '',
      },
    });
    if (!result?.digest) {
      throw new Error('Failed to sign and execute transaction');
    }
    return result;
  }
  _stake({ validatorAddress }) {
    console.log('suiProvider::_stake, validatorAddress:', validatorAddress);
  }
  async _signMessage({ message, account }) {
    console.log('suiProvider::_signMessage, message:', message, 'account:', account);
    const result = await this._hookRequest({
      method: 'sui_signMessage',
      params: {
        message: toB64(message),
        accountAddress: account?.address || this._accounts[0]?.address,
      },
    });
    if (!result || typeof result !== 'string') {
      throw new Error('Failed to sign message');
    }
    return {
      signature: result,
    };
  }
  _setAccounts(addresses) {
    this._accounts = addresses.filter(Boolean).map(
      (address) =>
        new ReadonlyWalletAccount({
          address,
          publicKey: new Uint8Array(),
          chains: this._activeChain ? [this._activeChain] : [],
          features: ['sui:signAndExecuteTransaction'],
        }),
    );
  }
  _getActiveNetwork() {
    return { env: API_ENV.testNet };
  }
  _setActiveChain({ env }) {
    this._activeChain = env === API_ENV.customRPC ? 'sui:unknown' : API_ENV_TO_CHAIN[env];
  }
  _hookRequest(paramsObj) {
    return new Promise((resolve, reject) => {
      const _guid = guid();
      if (typeof paramsObj === 'string') {
        paramsObj = {
          method: paramsObj,
        };
      }
      paramsObj = {
        ...paramsObj,
        mark: {
          id: _guid,
          method: paramsObj.method,
          type: 'call_method',
        },
        provider: 'sui',
      };
      window.parent.postMessage(
        {
          from: DAPP_INPAGE,
          target: OPENBLOCK_CONTENT,
          data: paramsObj,
        },
        '*',
      );
      this.callBackList[_guid] = {
        resolve,
        reject,
      };
    });
  }
  _handleNormalMethod(mark, event) {
    if (event.data.value.data?.error) {
      this.callBackList[mark.id]?.['reject'](event.data.value.data?.error);
    } else {
      this.callBackList[mark.id]?.['resolve'](event.data.value.data?.result);
    }
    delete this.callBackList[mark.id];
  }
}
const initializeProvider = () => {
  const provider = new SuiProvider();
  try {
    delete window.suiWallet;
    Object.defineProperty(window, 'obsui', {
      enumerable: false,
      configurable: false,
      writable: false,
      value: provider,
    });
  } catch (e) {
    console.error(
      `[sui-wallet] Unable to attach to window.suiWallet. There are likely multiple copies of the Sui Wallet installed.`,
    );
  }
};
if (typeof window !== 'undefined') {
  initializeProvider();
}