Sign many transactions at once (#60)

This commit is contained in:
Armani Ferrante 2020-12-11 07:19:04 -08:00 committed by GitHub
parent d1e6ad7e18
commit 1f6e11107d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 160 additions and 89 deletions

View File

@ -78,7 +78,10 @@ export default function PopupPage({ opener }) {
useEffect(() => { useEffect(() => {
function messageHandler(e) { function messageHandler(e) {
if (e.origin === origin && e.source === window.opener) { if (e.origin === origin && e.source === window.opener) {
if (e.data.method !== 'signTransaction') { if (
e.data.method !== 'signTransaction' &&
e.data.method !== 'signAllTransactions'
) {
postMessage({ error: 'Unsupported method', id: e.data.id }); postMessage({ error: 'Unsupported method', id: e.data.id });
} }
@ -106,11 +109,29 @@ export default function PopupPage({ opener }) {
if (requests.length > 0) { if (requests.length > 0) {
const request = requests[0]; const request = requests[0];
assert(request.method === 'signTransaction'); assert(
const message = bs58.decode(request.params.message); request.method === 'signTransaction' ||
request.method === 'signAllTransactions',
);
async function sendSignature() { let messages =
request.method === 'signTransaction'
? [bs58.decode(request.params.message)]
: request.params.messages.map((m) => bs58.decode(m));
async function onApprove() {
setRequests((requests) => requests.slice(1)); setRequests((requests) => requests.slice(1));
if (request.method === 'signTransaction') {
sendSignature(messages[0]);
} else {
sendAllSignatures(messages);
}
if (requests.length === 1) {
focusParent();
}
}
async function sendSignature(message) {
postMessage({ postMessage({
result: { result: {
signature: await wallet.createSignature(message), signature: await wallet.createSignature(message),
@ -118,9 +139,19 @@ export default function PopupPage({ opener }) {
}, },
id: request.id, id: request.id,
}); });
if (requests.length === 1) {
focusParent();
} }
async function sendAllSignatures(messages) {
const signatures = await Promise.all(
messages.map((m) => wallet.createSignature(m)),
);
postMessage({
result: {
signatures,
publicKey: wallet.publicKey.toBase58(),
},
id: request.id,
});
} }
function sendReject() { function sendReject() {
@ -138,8 +169,8 @@ export default function PopupPage({ opener }) {
key={request.id} key={request.id}
autoApprove={autoApprove} autoApprove={autoApprove}
origin={origin} origin={origin}
message={message} messages={messages}
onApprove={sendSignature} onApprove={onApprove}
onReject={sendReject} onReject={sendReject}
/> />
); );
@ -269,7 +300,7 @@ function ApproveConnectionForm({ origin, onApprove }) {
); );
} }
function isSafeInstruction(publicKeys, owner, instructions) { function isSafeInstruction(publicKeys, owner, txInstructions) {
let unsafe = false; let unsafe = false;
const states = { const states = {
CREATED: 0, CREATED: 0,
@ -292,6 +323,7 @@ function isSafeInstruction(publicKeys, owner, instructions) {
return accountStates[pubkey.toBase58()] === states.OWNED; return accountStates[pubkey.toBase58()] === states.OWNED;
} }
txInstructions.forEach((instructions) => {
instructions.forEach((instruction) => { instructions.forEach((instruction) => {
if (!instruction) { if (!instruction) {
unsafe = true; unsafe = true;
@ -347,6 +379,7 @@ function isSafeInstruction(publicKeys, owner, instructions) {
} }
} }
}); });
});
// Check that all accounts are owned // Check that all accounts are owned
if ( if (
@ -363,7 +396,7 @@ function isSafeInstruction(publicKeys, owner, instructions) {
function ApproveSignatureForm({ function ApproveSignatureForm({
origin, origin,
message, messages,
onApprove, onApprove,
onReject, onReject,
autoApprove, autoApprove,
@ -375,24 +408,30 @@ function ApproveSignatureForm({
const [publicKeys] = useWalletPublicKeys(); const [publicKeys] = useWalletPublicKeys();
const [parsing, setParsing] = useState(true); const [parsing, setParsing] = useState(true);
const [instructions, setInstructions] = useState(null); // An array of arrays, where each element is the set of instructions for a
// single transaction.
const [txInstructions, setTxInstructions] = useState(null);
const buttonRef = useRef(); const buttonRef = useRef();
const isMultiTx = messages.length > 1;
useEffect(() => { useEffect(() => {
decodeMessage(connection, wallet, message).then((instructions) => { Promise.all(messages.map((m) => decodeMessage(connection, wallet, m))).then(
setInstructions(instructions); (txInstructions) => {
setTxInstructions(txInstructions);
setParsing(false); setParsing(false);
}); },
}, [message, connection, wallet]); );
}, [messages, connection, wallet]);
const validator = useMemo(() => { const validator = useMemo(() => {
return { return {
safe: safe:
publicKeys && publicKeys &&
instructions && txInstructions &&
isSafeInstruction(publicKeys, wallet.publicKey, instructions), isSafeInstruction(publicKeys, wallet.publicKey, txInstructions),
}; };
}, [publicKeys, instructions, wallet]); }, [publicKeys, txInstructions, wallet]);
useEffect(() => { useEffect(() => {
if (validator.safe && autoApprove) { if (validator.safe && autoApprove) {
@ -460,6 +499,37 @@ function ApproveSignatureForm({
} }
}; };
const txLabel = (idx) => {
return (
<>
<Typography variant="h6" gutterBottom>
Transaction {idx.toString()}
</Typography>
<Divider style={{ marginTop: 20 }} />
</>
);
};
const txListItem = (instructions, txIdx) => {
const ixs = instructions.map((instruction, i) => (
<Box style={{ marginTop: 20 }} key={i}>
{getContent(instruction)}
<Divider style={{ marginTop: 20 }} />
</Box>
));
if (!isMultiTx) {
return ixs;
}
return (
<Box style={{ marginTop: 20 }} key={txIdx}>
{txLabel(txIdx)}
{ixs}
</Box>
);
};
return ( return (
<Card> <Card>
<CardContent> <CardContent>
@ -478,27 +548,26 @@ function ApproveSignatureForm({
style={{ fontWeight: 'bold' }} style={{ fontWeight: 'bold' }}
gutterBottom gutterBottom
> >
Parsing transaction: Parsing transaction{isMultiTx > 0 ? 's' : ''}:
</Typography> </Typography>
</div> </div>
<Typography style={{ wordBreak: 'break-all' }}> {messages.map((message, idx) => (
<Typography key={idx} style={{ wordBreak: 'break-all' }}>
{bs58.encode(message)} {bs58.encode(message)}
</Typography> </Typography>
))}
</> </>
) : ( ) : (
<> <>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
{instructions {txInstructions
? `${origin} wants to:` ? `${origin} wants to:`
: `Unknown transaction data`} : `Unknown transaction data`}
</Typography> </Typography>
{instructions ? ( {txInstructions ? (
instructions.map((instruction, i) => ( txInstructions.map((instructions, txIdx) =>
<Box style={{ marginTop: 20 }} key={i}> txListItem(instructions, txIdx),
{getContent(instruction)} )
<Divider style={{ marginTop: 20 }} />
</Box>
))
) : ( ) : (
<> <>
<Typography <Typography
@ -506,11 +575,13 @@ function ApproveSignatureForm({
style={{ fontWeight: 'bold' }} style={{ fontWeight: 'bold' }}
gutterBottom gutterBottom
> >
Unknown transaction: Unknown transaction{isMultiTx > 0 ? 's' : ''}:
</Typography> </Typography>
{messages.map((message) => (
<Typography style={{ wordBreak: 'break-all' }}> <Typography style={{ wordBreak: 'break-all' }}>
{bs58.encode(message)} {bs58.encode(message)}
</Typography> </Typography>
))}
</> </>
)} )}
{!validator.safe && ( {!validator.safe && (
@ -543,7 +614,7 @@ function ApproveSignatureForm({
color="primary" color="primary"
onClick={onApprove} onClick={onApprove}
> >
Approve Approve{isMultiTx ? ' All' : ''}
</Button> </Button>
</CardActions> </CardActions>
</Card> </Card>