Skip to content

Commit

Permalink
[wallet-ext][sdk] Introduce generic dapp transaction signing interface (
Browse files Browse the repository at this point in the history
MystenLabs#4904)

* Add new interface

* Start adding signAndSendTransaction to dapp interrface

* Add support for signable transactions

* Add changeset

* Flow types through

* Fix error printing

* Implement new APIs through dapp interface

* Remove storage clear

* Remove incorrect experimental
  • Loading branch information
Jordan-Mysten authored Oct 3, 2022
1 parent e011249 commit ccf7f14
Show file tree
Hide file tree
Showing 16 changed files with 407 additions and 181 deletions.
5 changes: 5 additions & 0 deletions .changeset/ten-experts-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mysten/sui.js": minor
---

Added generic signAndExecuteTransaction method to the SDK, which can be used with any supported type of transaction.
19 changes: 18 additions & 1 deletion apps/wallet/src/dapp-interface/DAppInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import { WindowMessageStream } from '_messaging/WindowMessageStream';
import { isErrorPayload } from '_payloads';
import { ALL_PERMISSION_TYPES } from '_payloads/permissions';

import type { SuiAddress, MoveCallTransaction } from '@mysten/sui.js';
import type {
SuiAddress,
MoveCallTransaction,
SignableTransaction,
} from '@mysten/sui.js';
import type { Payload } from '_payloads';
import type { GetAccount } from '_payloads/account/GetAccount';
import type { GetAccountResponse } from '_payloads/account/GetAccountResponse';
Expand Down Expand Up @@ -86,6 +90,19 @@ export class DAppInterface {
);
}

public signAndExecuteTransaction(transaction: SignableTransaction) {
return mapToPromise(
this.send<ExecuteTransactionRequest, ExecuteTransactionResponse>({
type: 'execute-transaction-request',
transaction: {
type: 'v2',
data: transaction,
},
}),
(response) => response.result
);
}

public executeMoveCall(transaction: MoveCallTransaction) {
return mapToPromise(
this.send<ExecuteTransactionRequest, ExecuteTransactionResponse>({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

import { isBasePayload } from '_payloads';

import type { MoveCallTransaction } from '@mysten/sui.js';
import type { MoveCallTransaction, SignableTransaction } from '@mysten/sui.js';
import type { BasePayload, Payload } from '_payloads';

export type TransactionDataType =
| { type: 'v2'; data: SignableTransaction }
| { type: 'move-call'; data: MoveCallTransaction }
| { type: 'serialized-move-call'; data: string };

Expand Down
57 changes: 34 additions & 23 deletions apps/wallet/src/ui/app/pages/dapp-tx-approval/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -189,29 +189,40 @@ export function DappTxApprovalPage() {
}
}, [loading, txRequest]);

const valuesContent = useMemo(
() =>
txRequest?.tx?.type === 'move-call'
? [
{ label: 'Transaction Type', content: 'MoveCall' },
{
label: 'Function',
content: txRequest.tx.data.function,
},
{
label: 'Gas Fees',
content: txRequest.tx.data.gasBudget,
},
]
: [
{
label: 'Transaction Type',
content: 'SerializedMoveCall',
},
{ label: 'Contents', content: txRequest?.tx?.data },
],
[txRequest]
);
const valuesContent = useMemo(() => {
switch (txRequest?.tx.type) {
case 'v2': {
return [
{
label: 'Transaction Type',
content: txRequest.tx.data.kind,
},
];
}
case 'move-call':
return [
{ label: 'Transaction Type', content: 'MoveCall' },
{
label: 'Function',
content: txRequest.tx.data.function,
},
{
label: 'Gas Fees',
content: txRequest.tx.data.gasBudget,
},
];
case 'serialized-move-call':
return [
{
label: 'Transaction Type',
content: 'SerializedMoveCall',
},
{ label: 'Contents', content: txRequest?.tx?.data },
];
default:
return [];
}
}, [txRequest]);

return (
<Loading loading={loading}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,11 @@ export const respondToTransactionRequest = createAsyncThunk<
if (approved) {
const signer = api.getSignerInstance(keypairVault.getKeyPair());
try {
if (txRequest.tx.type === 'move-call') {
if (txRequest.tx.type === 'v2') {
txResult = await signer.signAndExecuteTransaction(
txRequest.tx.data
);
} else if (txRequest.tx.type === 'move-call') {
txResult = await signer.executeMoveCall(txRequest.tx.data);
} else if (txRequest.tx.type === 'serialized-move-call') {
const txBytes = new Base64DataBuffer(txRequest.tx.data);
Expand Down
105 changes: 84 additions & 21 deletions sdk/typescript/src/signers/signer-with-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
TransferSuiTransaction,
TxnDataSerializer,
PublishTransaction,
SignableTransaction,
} from './txn-data-serializers/txn-data-serializer';

///////////////////////////////
Expand Down Expand Up @@ -62,38 +63,100 @@ export abstract class SignerWithProvider implements Signer {

/**
* Sign a transaction and submit to the Gateway for execution
* @experimental
* @param txBytes BCS serialised TransactionData bytes
*/
async signAndExecuteTransaction(
txBytes: Base64DataBuffer
transaction: Base64DataBuffer | SignableTransaction
): Promise<SuiTransactionResponse> {
const sig = await this.signData(txBytes);
return await this.provider.executeTransaction(
txBytes.toString(),
sig.signatureScheme,
sig.signature.toString(),
sig.pubKey.toString()
);
// Handle submitting raw transaction bytes:
if (
transaction instanceof Base64DataBuffer ||
transaction.kind === 'bytes'
) {
const txBytes =
transaction instanceof Base64DataBuffer
? transaction
: new Base64DataBuffer(transaction.data);

const sig = await this.signData(txBytes);
return await this.provider.executeTransaction(
txBytes.toString(),
sig.signatureScheme,
sig.signature.toString(),
sig.pubKey.toString()
);
}

switch (transaction.kind) {
case 'moveCall':
return this.executeMoveCall(transaction.data);
case 'transferSui':
return this.transferSui(transaction.data);
case 'transferObject':
return this.transferObject(transaction.data);
case 'mergeCoin':
return this.mergeCoin(transaction.data);
case 'splitCoin':
return this.splitCoin(transaction.data);
case 'pay':
return this.pay(transaction.data);
default:
throw new Error(
`Unknown transaction kind: "${(transaction as any).kind}"`
);
}
}

/**
* @experimental Sign a transaction and submit to the Fullnode for execution
*
* @param txBytes BCS serialised TransactionData bytes
*/
async signAndExecuteTransactionWithRequestType(
txBytes: Base64DataBuffer,
transaction: Base64DataBuffer | SignableTransaction,
requestType: ExecuteTransactionRequestType
): Promise<SuiExecuteTransactionResponse> {
const sig = await this.signData(txBytes);
return await this.provider.executeTransactionWithRequestType(
txBytes.toString(),
sig.signatureScheme,
sig.signature.toString(),
sig.pubKey.toString(),
requestType
);
// Handle submitting raw transaction bytes:
if (
transaction instanceof Base64DataBuffer ||
transaction.kind === 'bytes'
) {
const txBytes =
transaction instanceof Base64DataBuffer
? transaction
: new Base64DataBuffer(transaction.data);

const sig = await this.signData(txBytes);
return await this.provider.executeTransactionWithRequestType(
txBytes.toString(),
sig.signatureScheme,
sig.signature.toString(),
sig.pubKey.toString(),
requestType
);
}

switch (transaction.kind) {
case 'moveCall':
return this.executeMoveCallWithRequestType(
transaction.data,
requestType
);
case 'transferSui':
return this.transferSuiWithRequestType(transaction.data, requestType);
case 'transferObject':
return this.transferObjectWithRequestType(
transaction.data,
requestType
);
case 'mergeCoin':
return this.mergeCoinWithRequestType(transaction.data, requestType);
case 'splitCoin':
return this.splitCoinWithRequestType(transaction.data, requestType);
case 'pay':
return this.payWithRequestType(transaction.data, requestType);
default:
throw new Error(
`Unknown transaction kind: "${(transaction as any).kind}"`
);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export class RpcTxnDataSerializer implements TxnDataSerializer {
);
return new Base64DataBuffer(resp.txBytes);
} catch (err) {
throw new Error(`Error transferring object: ${err} with args ${t}`);
throw new Error(
`Error transferring object: ${err} with args ${JSON.stringify(t)}`
);
}
}

Expand All @@ -72,7 +74,9 @@ export class RpcTxnDataSerializer implements TxnDataSerializer {
);
return new Base64DataBuffer(resp.txBytes);
} catch (err) {
throw new Error(`Error transferring Sui coin: ${err} with args ${t}`);
throw new Error(
`Error transferring Sui coin: ${err} with args ${JSON.stringify(t)}`
);
}
}

Expand All @@ -96,7 +100,9 @@ export class RpcTxnDataSerializer implements TxnDataSerializer {
);
return new Base64DataBuffer(resp.txBytes);
} catch (err) {
throw new Error(`Error executing Pay transaction: ${err} with args ${t}`);
throw new Error(
`Error executing Pay transaction: ${err} with args ${JSON.stringify(t)}`
);
}
}

Expand All @@ -122,7 +128,9 @@ export class RpcTxnDataSerializer implements TxnDataSerializer {
);
return new Base64DataBuffer(resp.txBytes);
} catch (err) {
throw new Error(`Error executing a move call: ${err} with args ${t}`);
throw new Error(
`Error executing a move call: ${err} with args ${JSON.stringify(t)}`
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,40 @@ export interface MoveCallTransaction {
gasBudget: number;
}

/** A type that represents the possible transactions that can be signed: */
export type SignableTransaction =
| {
kind: 'moveCall';
data: MoveCallTransaction;
}
| {
kind: 'transferSui';
data: TransferSuiTransaction;
}
| {
kind: 'transferObject';
data: TransferObjectTransaction;
}
| {
kind: 'mergeCoin';
data: MergeCoinTransaction;
}
| {
kind: 'splitCoin';
data: SplitCoinTransaction;
}
| {
kind: 'pay';
data: PayTransaction;
}
| {
kind: 'bytes';
data: Uint8Array;
};

export type SignableTransactionKind = SignableTransaction['kind'];
export type SignableTransactionData = SignableTransaction['data'];

/**
* Transaction type used for publishing Move modules to the Sui.
*
Expand Down
14 changes: 13 additions & 1 deletion sdk/wallet-adapter/packages/adapters/base-adapter/src/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import {
MoveCallTransaction,
SignableTransaction,
SuiAddress,
SuiTransactionResponse,
} from "@mysten/sui.js";
Expand All @@ -15,11 +16,22 @@ export interface WalletCapabilities {
// Connection Management
connect: () => Promise<void>;
disconnect: () => Promise<void>;
// DappInterfaces

/**
* Suggest a transaction for the user to sign. Supports all valid transaction types.
*/
signAndExecuteTransaction?(
transaction: SignableTransaction
): Promise<SuiTransactionResponse>;

getAccounts: () => Promise<SuiAddress[]>;

/** @deprecated Prefer `signAndExecuteTransaction` when available. */
executeMoveCall: (
transaction: MoveCallTransaction
) => Promise<SuiTransactionResponse>;

/** @deprecated Prefer `signAndExecuteTransaction` when available. */
executeSerializedMoveCall: (
transactionBytes: Uint8Array
) => Promise<SuiTransactionResponse>;
Expand Down
Loading

0 comments on commit ccf7f14

Please sign in to comment.