sui
OpenBlock loads Dapp via an iframe, and the Dapp interacts with the OpenBlock wallet using the standard web3 rpc methods
tip
use window.obsui replace window.sui.
OpenBlock will send a message to the DApp when the Dapp is loaded. The Dapp can use this message to determine if the host application is OpenBlock and then load 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();
}