Skip to content

Commit

Permalink
Merge pull request #8 from Access-Labs-Inc/fix/confirmation-flow-and-…
Browse files Browse the repository at this point in the history
…use-our-rpcs

Update the confirmation flow and add our RPCs (wrpc)
  • Loading branch information
martincik authored Jul 12, 2024
2 parents 58f27a9 + 147aa54 commit afa7e69
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 61 deletions.
6 changes: 3 additions & 3 deletions html-dev/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ <h2>The shown widget is for demonstration purpose only</h2>
_acs_blink('init', {
element: document.getElementById('acs-blink'),
debug: true,
poolName: "The Block",
poolId: 'Fxh4hDFHJuTfD3Eq4en36dTk8QvbsSMoTE5Y2hVX3qVt',
poolSlug: 'the-block',
poolName: "Access",
poolId: 'GesQkZr8R9yVS8rCuMBmfzKJh3kfyFgq6NV9k17aKQnd',
poolSlug: 'access-protocol',
showTitle: true,
showDescription: true,
showImage: true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"preact": "^10.2.1",
"rc-util": "^5.24.4",
"react-input-slider": "^6.0.1",
"vm-browserify": "^1.1.2",
"zod": "^3.23.8"
},
"resolutions": {
Expand Down
116 changes: 58 additions & 58 deletions src/components/dialect/ui/ActionContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,59 @@ import React, { h } from 'preact';
import { useEffect, useMemo, useReducer, useState, useContext } from 'preact/compat';
import { Buffer } from 'buffer';
import { ActionLayout, ButtonProps } from './ActionLayout';
import { VersionedTransaction } from '@solana/web3.js';
import { Connection, VersionedTransaction } from '@solana/web3.js';
import { Action, ActionComponent } from '../api/Action';
import { useWallet } from '@solana/wallet-adapter-react';
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
import { useWalletModal } from '@solana/wallet-adapter-react-ui';
import ActionLayoutSkeleton from '../ui/ActionLayoutSkeleton';
import { PublicConnection } from '../utils/public-connection';
import { Snackbar } from '../ui/Snackbar';
import { ConfigContext } from '../../../AppContext';
import {
ActionsRegistry,
getExtendedActionState,
} from '../api/ActionsRegistry';
import { ClusterTarget } from '../constants';
import { toSpliced } from '../../../libs/utils';
import { isTimeoutError, timeout, toSpliced } from '../../../libs/utils';
import { sleep } from '@accessprotocol/js';

const CONFIRM_TIMEOUT_MS = 60000 * 1.2; // 20% extra time
const CONFIRM_STATUS = "confirmed";

const confirmTransaction = (
const confirmTransaction = async (
signature: string,
cluster: ClusterTarget = 'mainnet',
connection: Connection
) => {
return new Promise<void>((res, rej) => {
const start = Date.now();

const confirm = async () => {
if (Date.now() - start >= CONFIRM_TIMEOUT_MS) {
rej(new Error('Unable to confirm transaction'));
return;
}

try {
const status =
await PublicConnection.getInstance(
cluster,
).connection.getSignatureStatus(signature);

// if error present, transaction failed
if (status.value?.err) {
rej(new Error('Transaction execution failed'));
return;
}
for (let i=0; i<90; i+=1) {
try {
const status = await timeout(
connection.getSignatureStatus(signature, {
searchTransactionHistory: true
}),
CONFIRM_TIMEOUT_MS,
"Timing out waiting for signature"
);

// if has confirmations, transaction is successful
if (status.value && status.value.confirmations !== null) {
res();
if (isTimeoutError(status)) {
console.log(`Confirming signature ${signature} attemp ${i} ~ 1s sleep`);
await sleep(1000);
} else if (status.value?.err) {
throw new Error('Transaction execution failed');
} else {
const statusValue = status.value?.confirmationStatus;
if (statusValue === CONFIRM_STATUS || statusValue === "finalized") {
return;
}
} catch (e) {
}
} catch (e) {
if (isTimeoutError(e)) {
console.log(`Confirming signature ${signature} attemp ${i} ~ 1s sleep`);
await sleep(1000);
} else {
console.error('Error confirming transaction', e);
throw e;
}

setTimeout(confirm, 3000);
};

confirm();
});
}
}
};

type ExecutionStatus = 'blocked' | 'idle' | 'executing' | 'success' | 'error';
Expand Down Expand Up @@ -168,7 +165,6 @@ export const ActionContainer = ({
showTitle,
showDescription,
showWebsite,
cluster = 'mainnet',
}: {
initialApiUrl: string;
websiteUrl?: string;
Expand All @@ -181,6 +177,7 @@ export const ActionContainer = ({
const { publicKey, sendTransaction } = useWallet();
const { setVisible: setWalletModalVisible } = useWalletModal();
const { element } = useContext(ConfigContext);
const { connection } = useConnection();

const [action, setAction] = useState<Action | null>(null);
const [actionState, setActionState] = useState(
Expand Down Expand Up @@ -312,6 +309,7 @@ export const ActionContainer = ({

const execute = async (
component: ActionComponent,
connection: Connection,
params?: Record<string, string>,
) => {
if (component.parameter && params) {
Expand Down Expand Up @@ -339,15 +337,15 @@ export const ActionContainer = ({

const result = await sendTransaction(
VersionedTransaction.deserialize(Buffer.from(tx.transaction, 'base64')),
PublicConnection.getInstance(cluster).connection,
connection,
)
.then((signature) => ({ signature, error: null }))
.catch((e) => ({ signature: null, error: e }));

if (result.error || !result.signature) {
dispatch({ type: ExecutionType.RESET });
} else {
await confirmTransaction(result.signature, cluster);
await confirmTransaction(result.signature, connection);
dispatch({
type: ExecutionType.FINISH,
successMessage: tx.message,
Expand All @@ -372,25 +370,27 @@ export const ActionContainer = ({
}
};

const asButtonProps = (it: ActionComponent): ButtonProps => ({
text: buttonLabelMap[executionState.status] ?? it.label,
loading:
executionState.status === 'executing' &&
it === executionState.executingAction,
disabled: action.disabled || executionState.status !== 'idle',
variant: buttonVariantMap[executionState.status],
onClick: (params?: Record<string, string>) => execute(it, params),
});

const asInputProps = (it: ActionComponent) => {
return {
// since we already filter this, we can safely assume that parameter is not null
placeholder: it.parameter!.label,
const asButtonProps = (connection: Connection) =>
(it: ActionComponent): ButtonProps => ({
text: buttonLabelMap[executionState.status] ?? it.label,
loading:
executionState.status === 'executing' &&
it === executionState.executingAction,
disabled: action.disabled || executionState.status !== 'idle',
name: it.parameter!.name,
button: asButtonProps(it),
variant: buttonVariantMap[executionState.status],
onClick: (params?: Record<string, string>) => execute(it, connection, params),
});

const asInputProps = (connection: Connection) =>
(it: ActionComponent) => {
return {
// since we already filter this, we can safely assume that parameter is not null
placeholder: it.parameter!.label,
disabled: action.disabled || executionState.status !== 'idle',
name: it.parameter!.name,
button: asButtonProps(connection)(it),
};
};
};

return (
<ActionLayout
Expand All @@ -406,8 +406,8 @@ export const ActionContainer = ({
: null
}
success={executionState.successMessage}
buttons={buttons.map(asButtonProps)}
inputs={inputs.map(asInputProps)}
buttons={buttons.map(asButtonProps(connection))}
inputs={inputs.map(asInputProps(connection))}
disclaimer={disclaimer}
/>
);
Expand Down
24 changes: 24 additions & 0 deletions src/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,27 @@ export function toSpliced<T>(arr: T[] | undefined, n: number): T[] {
carr.splice(n);
return carr;
}

class TimeoutError extends Error {}

export function isTimeoutError(err: any): err is TimeoutError {
return err instanceof TimeoutError;
}

export function timeout<T>(
promise: Promise<T>,
time: number,
exceptionValue: string,
): Promise<T | Error> {
let timer: ReturnType<typeof setTimeout>;
return Promise.race([
promise,
new Promise<T | Error>((res, rej) => {
timer = setTimeout(() => {
rej(new TimeoutError(exceptionValue));
}, time);
}),
]).finally(() => {
clearTimeout(timer);
}) as Promise<T | Error>;
}
1 change: 1 addition & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ module.exports = (env) => {
path: require.resolve("path-browserify"),
buffer: require.resolve("buffer"),
zlib: require.resolve("browserify-zlib"),
vm: require.resolve("vm-browserify"),
},
},
},
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8332,6 +8332,11 @@ vary@~1.1.2:
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==

vm-browserify@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0"
integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==

w3c-xmlserializer@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz"
Expand Down

0 comments on commit afa7e69

Please sign in to comment.