Skip to content

Commit

Permalink
Wallet Connect Middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
acedward committed Dec 15, 2023
1 parent faa9e87 commit e7a465e
Show file tree
Hide file tree
Showing 4 changed files with 212 additions and 34 deletions.
2 changes: 1 addition & 1 deletion packages/engine/paima-runtime/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function getOpenApiJson(userStateMachineApi: object | undefined): object {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const mergeResult = merge([{ oas: basicControllerJson as any }, { oas: userStateMachineApi }]);
if (isErrorResult(mergeResult)) {
logError('Failed to merge openAPI definitions');
logError(`Failed to merge openAPI definitions: ${JSON.stringify(mergeResult)}`);
return userStateMachineApi;
}
return mergeResult.output;
Expand Down
61 changes: 28 additions & 33 deletions packages/engine/paima-sm/src/delegate-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class DelegateWallet {
/* Verify Signature with all possible wallets. */
private async verifySignature(
walletAddress: string,
internalMessage: string,
message: string,
signature: string
): Promise<boolean> {
if (!walletAddress || !signature) throw new Error('No Signature');
Expand All @@ -97,7 +97,6 @@ export class DelegateWallet {
CryptoManager.Cardano(),
CryptoManager.Polkadot(),
];
const message = this.generateMessage(internalMessage);
for (const wallet of wallets) {
try {
if (await wallet.verifySignature(walletAddress, message, signature)) {
Expand Down Expand Up @@ -136,10 +135,10 @@ export class DelegateWallet {
await newAddress.run({ address: addressB }, this.DBConn);
}

private async cmdDelegate(from: string, to: string): Promise<void> {
const fromAddress = await this.getOrCreateNewAddress(from);
const toAddress = await this.getOrCreateNewAddress(to);

private async validateDelegate(
fromAddress: { address: IGetAddressFromAddressResult; isNew: boolean },
toAddress: { address: IGetAddressFromAddressResult; isNew: boolean }
): Promise<void> {
// Check if delegation or reverse delegation does not exist TODO.
const [currentDelegation] = await getDelegation.run(
{ from_id: fromAddress.address.id, set_id: toAddress.address.id },
Expand All @@ -152,40 +151,36 @@ export class DelegateWallet {
);
if (reverseDelegation) throw new Error('Reverse Delegation already exists');

// Case 1:
// If A->Y && B->Y, if B is new.
// Check if address needs to be replaced.
// This is a special case where FROM takes TO identity.
// As the identity is taken, no other updates are required.
const toAddressHasFrom = await getDelegationsFrom.run(
{ from_id: toAddress.address.id },
this.DBConn
);
if (toAddressHasFrom.length > 0) {
if (fromAddress.isNew) {
await this.swap(fromAddress.address.address, toAddress.address.address);

const [newToAddressId] = await getAddressFromAddress.run(
{ address: toAddress.address.address },
this.DBConn
);
await newDelegation.run(
{ from_id: toAddress.address.id, set_id: newToAddressId.id },
this.DBConn
);
DelegateWallet.addressCache.clear();
return;
}

// Cannot merge if both have progress.
if (!fromAddress.isNew && !toAddress.isNew) {
throw new Error('Both A and B have progress. Cannot merge.');
}

// If "TO" has "TO" delegations, it is already owned by another wallet.
// To reuse this address, cancel delegations first.
const [toAddressHasTo] = await getDelegationsTo.run(
{ set_id: toAddress.address.id },
this.DBConn
);
if (toAddressHasTo) {
throw new Error('Both A and B have progress. Cannot merge.');
if (toAddressHasTo) throw new Error('To Address has delegations. Cannot merge.');
}

private async cmdDelegate(from: string, to: string): Promise<void> {
const fromAddress = await this.getOrCreateNewAddress(from);
const toAddress = await this.getOrCreateNewAddress(to);
await this.validateDelegate(fromAddress, toAddress);

// Case 1:
// "from" is New, "to" has progress.
// swap IDs.
if (fromAddress.isNew && !toAddress.isNew) {
await this.swap(fromAddress.address.address, toAddress.address.address);
const [newToAddressId] = await getAddressFromAddress.run(
{ address: toAddress.address.address },
this.DBConn
);
fromAddress.address.id = toAddress.address.id;
toAddress.address.id = newToAddressId.id;
}

// Case 2:
Expand Down
1 change: 1 addition & 0 deletions packages/paima-sdk/paima-mw-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const paimaEndpoints = {
...utilityEndpoints,
};

export * from './wallet-connect/index.js';
export type * from './errors.js';
// Only for use in game-specific middleware:
export * from './types.js';
Expand Down
182 changes: 182 additions & 0 deletions packages/paima-sdk/paima-mw-core/src/wallet-connect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* delegate = &wd|from|to|from_signature|to_signature
* migrate = &wm|from|to|from_signature|to_signature
* cancelDelegations = &wc|to|to_signature
*/

import { builder } from '@paima/concise';
import { buildEndpointErrorFxn, PaimaMiddlewareErrorCode } from '../errors.js';
import { awaitBlock } from '../helpers/auxiliary-queries.js';
import { postConciseData } from '../helpers/posting.js';
import type { FailedResult, SuccessfulResult, PostDataResponse, LoginInfo } from '../types.js';
import { WalletMode } from '@paima/providers/src/utils.js';
import {
EvmInjectedConnector,
CardanoConnector,
PolkadotConnector,
AlgorandConnector,
} from '@paima/providers';
import { ENV } from '@paima/utils';
import { paimaEndpoints } from '../index.js';

export async function walletConnect(
from: string,
to: string,
from_signature: string,
to_signature: string
): Promise<SuccessfulResult<PostDataResponse> | FailedResult> {
const errorFxn = buildEndpointErrorFxn('wallet-connect');

// walletConnect = &wc|from|to|from_signature|to_signature
const conciseBuilder = builder.initialize();
conciseBuilder.setPrefix('&wd');
conciseBuilder.addValue({ value: from });
conciseBuilder.addValue({ value: to });
conciseBuilder.addValue({ value: from_signature });
conciseBuilder.addValue({ value: to_signature });
try {
const result = await postConciseData(conciseBuilder.build(), errorFxn);
if (!result.success) {
return errorFxn(PaimaMiddlewareErrorCode.ERROR_POSTING_TO_CHAIN);
}
const blockHeightTX = result.blockHeight;
await awaitBlock(blockHeightTX);
return { success: true, result };
} catch (err) {
return errorFxn(PaimaMiddlewareErrorCode.ERROR_POSTING_TO_CHAIN, err);
}
}

export async function walletConnectMigrate(
from: string,
to: string,
from_signature: string,
to_signature: string
): Promise<SuccessfulResult<PostDataResponse> | FailedResult> {
const errorFxn = buildEndpointErrorFxn('wallet-connect');

// migrate = &wm|from|to|from_signature|to_signature
const conciseBuilder = builder.initialize();
conciseBuilder.setPrefix('&wm');
conciseBuilder.addValue({ value: from });
conciseBuilder.addValue({ value: to });
conciseBuilder.addValue({ value: from_signature });
conciseBuilder.addValue({ value: to_signature });
try {
const result = await postConciseData(conciseBuilder.build(), errorFxn);
if (!result.success) {
return errorFxn(PaimaMiddlewareErrorCode.ERROR_POSTING_TO_CHAIN);
}
const blockHeightTX = result.blockHeight;
await awaitBlock(blockHeightTX);
return { success: true, result };
} catch (err) {
return errorFxn(PaimaMiddlewareErrorCode.ERROR_POSTING_TO_CHAIN, err);
}
}

export async function walletConnectCancelDelegations(
to: string,
to_signature: string
): Promise<SuccessfulResult<PostDataResponse> | FailedResult> {
const errorFxn = buildEndpointErrorFxn('wallet-cancel-delegations');

// walletConnect = &wc|to|to_signature
const conciseBuilder = builder.initialize();
conciseBuilder.setPrefix('&wc');
conciseBuilder.addValue({ value: to });
conciseBuilder.addValue({ value: to_signature });
try {
const result = await postConciseData(conciseBuilder.build(), errorFxn);
if (!result.success) {
return errorFxn(PaimaMiddlewareErrorCode.ERROR_POSTING_TO_CHAIN);
}
const blockHeightTX = result.blockHeight;
await awaitBlock(blockHeightTX);
return { success: true, result };
} catch (err) {
return errorFxn(PaimaMiddlewareErrorCode.ERROR_POSTING_TO_CHAIN, err);
}
}

export type WalletConnectHelperTypes = 'EVM' | 'Cardano' | 'Polkadot' | 'Algorand';

export class WalletConnectHelper {
private static readonly DELEGATE_WALLET_PREFIX = 'DELEGATE-WALLET';
private static readonly SEP = ':';

public buildMessageToSign(subMessage: string): string {
return `${WalletConnectHelper.DELEGATE_WALLET_PREFIX}${
WalletConnectHelper.SEP
}${subMessage.toLocaleLowerCase()}${WalletConnectHelper.SEP}${ENV.CONTRACT_ADDRESS}`;
}

private async signWithExternalWallet(
walletType: WalletConnectHelperTypes,
message: string
): Promise<string> {
switch (walletType) {
case 'EVM':
return (await EvmInjectedConnector.instance().getProvider()?.signMessage(message)) || '';
case 'Cardano':
return (await CardanoConnector.instance().getProvider()?.signMessage(message)) || '';
case 'Polkadot':
return (await PolkadotConnector.instance().getProvider()?.signMessage(message)) || '';
case 'Algorand':
return (await AlgorandConnector.instance().getProvider()?.signMessage(message)) || '';
default:
throw new Error('Invalid wallet type');
}
}

public async connectExternalWalletAndSign(
walletType: WalletConnectHelperTypes,
walletName: string,
subMessage: string
): Promise<{
message: string;
signedMessage: string;
walletAddress: string;
}> {
let loginInfo: LoginInfo;
switch (walletType) {
case 'EVM':
loginInfo = {
mode: WalletMode.EvmInjected,
preferBatchedMode: false,
preference: {
name: walletName,
},
checkChainId: false,
};
break;
case 'Cardano':
loginInfo = {
mode: WalletMode.Cardano,
preference: {
name: walletName,
},
};
break;
case 'Polkadot':
loginInfo = { mode: WalletMode.Polkadot };
break;
case 'Algorand':
loginInfo = { mode: WalletMode.Algorand };
break;
default:
throw new Error('No supported wallet type.');
}

const response = await paimaEndpoints.userWalletLogin(loginInfo, false);
if (!response.success) throw new Error('Cannot connect wallet');

const toSign = this.buildMessageToSign(subMessage);

return {
message: toSign,
signedMessage: await this.signWithExternalWallet(walletType, toSign),
walletAddress: response.result.walletAddress,
};
}
}

0 comments on commit e7a465e

Please sign in to comment.