Skip to content

Commit

Permalink
fix: auth server and sdk when not using sessions (#45)
Browse files Browse the repository at this point in the history
  • Loading branch information
MexicanAce authored Dec 22, 2024
1 parent 412382f commit b9afdc3
Show file tree
Hide file tree
Showing 14 changed files with 575 additions and 50 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ jobs:
run: pnpm run deploy
working-directory: packages/contracts

- name: Install zksync-foundry
run: |
wget -qc https://github.com/matter-labs/foundry-zksync/releases/download/nightly/foundry_nightly_linux_amd64.tar.gz -O - | tar -xz
./forge -V && ./cast -V
sudo mv ./forge /usr/local/bin/
sudo mv ./cast /usr/local/bin/
forge -V && cast -V
- name: Deploy Demo-App contracts
run: pnpm nx deploy-contracts demo-app

# Run E2E tests
- name: Install Playwright Chromium Browser
run: pnpm exec playwright install chromium
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ node_modules/

# era-test-node
era_test_node.log
anvil-zksync.log
foundryup-zksync
cache/
zkout/

package-lock.json
yarn.lock
Expand Down
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,19 @@ This monorepo is comprised of the following packages, products, and examples:
[workspace protocol](https://pnpm.io/workspaces#workspace-protocol-workspace)
to link SDK in the new folder.

3. Start a local node:
3. Install `foundry-zksync`:

```bash
curl -L https://raw.githubusercontent.com/matter-labs/foundry-zksync/main/install-foundry-zksync | bash
```

4. Start a local node:

```bash
npx zksync-cli dev start
```

4. Compile and deploy contracts to the local node:
5. Compile and deploy contracts to the local node:

```bash
# Compile and deploy contracts
Expand All @@ -140,11 +146,9 @@ This monorepo is comprised of the following packages, products, and examples:
pnpm run deploy
```

5. Start the demo application:
6. Start the demo application:

```bash
# Go back to root folder to start demo app
cd ../..
pnpm nx dev demo-app
```

Expand Down
1 change: 1 addition & 0 deletions examples/demo-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ logs
.env
.env.*
!.env.example
forge-output.json
5 changes: 3 additions & 2 deletions examples/demo-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
"@matterlabs/zksync-contracts": "^0.6.1",
"@nuxtjs/google-fonts": "^3.2.0",
"@pinia/nuxt": "^0.5.5",
"@simplewebauthn/browser": "^10.0.0",
Expand All @@ -19,8 +20,8 @@
"viem": "2.21.14",
"vue": "^3.4.21",
"wagmi": "^2.12.17",
"zksync-sso": "workspace:*",
"zksync-ethers": "^6.15.0"
"zksync-ethers": "^6.15.0",
"zksync-sso": "workspace:*"
},
"devDependencies": {
"@nuxt/eslint": "^0.5.7",
Expand Down
84 changes: 69 additions & 15 deletions examples/demo-app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
ZKsync SSO Demo
</h1>
<button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="address ? disconnectWallet() : connectWallet()"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-4"
@click="address ? disconnectWallet() : connectWallet(false)"
>
{{ address ? "Disconnect" : "Connect" }}
</button>
<button
v-if="!address"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click="address ? disconnectWallet() : connectWallet(true)"
>
Connect w/ Session
</button>
<div
v-if="address"
class="mt-4"
Expand All @@ -23,12 +30,20 @@
</div>
<button
v-if="address"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3 mr-4 disabled:bg-slate-300"
:disabled="isSendingEth"
@click="sendTokens()"
@click="sendTokens(false)"
>
Send 0.1 ETH
</button>
<button
v-if="address"
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mt-3 disabled:bg-slate-300"
:disabled="isSendingEth"
@click="sendTokens(true)"
>
Send 0.1 ETH w/ Paymaster
</button>

<div
v-if="errorMessage"
Expand All @@ -45,11 +60,13 @@ import { zksyncSsoConnector } from "zksync-sso/connector";
import { zksyncInMemoryNode } from "@wagmi/core/chains";
import { createWalletClient, http, parseEther, type Address } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { getGeneralPaymasterInput } from "viem/zksync";
import PaymasterContract from "../forge-output.json";
const chain = zksyncInMemoryNode;
const testTransferTarget = "0x55bE1B079b53962746B2e86d12f158a41DF294A6";
const zksyncConnector = zksyncSsoConnector({
const zksyncConnectorWithSession = zksyncSsoConnector({
authServerUrl: "http://localhost:3002/confirm",
session: {
feeLimit: parseEther("0.1"),
Expand All @@ -61,6 +78,9 @@ const zksyncConnector = zksyncSsoConnector({
],
},
});
const zksyncConnector = zksyncSsoConnector({
authServerUrl: "http://localhost:3002/confirm",
});
const wagmiConfig = createConfig({
chains: [chain],
connectors: [zksyncConnector],
Expand All @@ -84,10 +104,20 @@ const fundAccount = async () => {
transport: http(),
});
await richClient.sendTransaction({
let transactionHash = await richClient.sendTransaction({
to: address.value,
value: parseEther("1"),
});
// FIXME: When not using sessions, sendTransaction returns a map and not a string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((transactionHash as any).value !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transactionHash = (transactionHash as any).value;
}
await waitForTransactionReceipt(wagmiConfig, {
hash: transactionHash,
});
};
watchAccount(wagmiConfig, {
Expand Down Expand Up @@ -118,11 +148,11 @@ watch(address, async () => {
balance.value = currentBalance;
}, { immediate: true });
const connectWallet = async () => {
const connectWallet = async (useSession: boolean) => {
try {
errorMessage.value = "";
connect(wagmiConfig, {
connector: zksyncConnector,
connector: useSession ? zksyncConnectorWithSession : zksyncConnector,
chainId: chain.id,
});
} catch (error) {
Expand All @@ -133,25 +163,45 @@ const connectWallet = async () => {
};
const disconnectWallet = async () => {
errorMessage.value = "";
await disconnect(wagmiConfig);
};
const sendTokens = async () => {
const sendTokens = async (usePaymaster: boolean) => {
if (!address.value) return;
errorMessage.value = "";
isSendingEth.value = true;
try {
const transactionHash = await sendTransaction(wagmiConfig, {
to: testTransferTarget,
value: parseEther("0.1"),
});
let transactionHash;
if (usePaymaster) {
transactionHash = await sendTransaction(wagmiConfig, {
to: testTransferTarget,
value: parseEther("0.1"),
paymaster: PaymasterContract.deployedTo as `0x${string}`,
paymasterInput: getGeneralPaymasterInput({ innerInput: "0x" }),
});
} else {
transactionHash = await sendTransaction(wagmiConfig, {
to: testTransferTarget,
value: parseEther("0.1"),
});
}
// FIXME: When not using sessions, sendTransaction returns a map and not a string
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((transactionHash as any).value !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transactionHash = (transactionHash as any).value;
}
const receipt = await waitForTransactionReceipt(wagmiConfig, {
hash: transactionHash,
});
balance.value = await getBalance(wagmiConfig, {
address: address.value,
});
const receipt = await waitForTransactionReceipt(wagmiConfig, { hash: transactionHash });
if (receipt.status === "reverted") throw new Error("Transaction reverted");
} catch (error) {
// eslint-disable-next-line no-console
Expand All @@ -162,6 +212,10 @@ const sendTokens = async () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transactionFailureDetails = (error as any).cause?.cause?.data?.originalError?.cause?.details;
}
if (!transactionFailureDetails) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
transactionFailureDetails = (error as any).cause?.details;
}
if (transactionFailureDetails) {
errorMessage.value = transactionFailureDetails;
Expand Down
22 changes: 20 additions & 2 deletions examples/demo-app/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"prefix": "Demo-App:"
}
]
}
},
"dependsOn": ["deploy-contracts"]
},
"build": {
"executor": "nx:run-commands",
Expand All @@ -28,8 +29,24 @@
"command": "pnpm nuxt generate"
}
]
},
"dependsOn": ["deploy-contracts"]
},
"build-contracts": {
"executor": "nx:run-commands",
"options": {
"cwd": "examples/demo-app",
"command": "forge build smart-contracts/DemoPaymaster.sol --root . --zksync"
}
},
"deploy-contracts": {
"executor": "nx:run-commands",
"options": {
"cwd": "examples/demo-app",
"command": "forge create smart-contracts/DemoPaymaster.sol:DemoPaymaster --private-key 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 --rpc-url http://localhost:8011 --root . --chain 260 --zksync --json 2>&1 | sed -n 's/.*\\({.*}\\).*/\\1/p' > forge-output.json && ADDRESS=$(sed -n 's/.*\"deployedTo\":\"\\([^\"]*\\)\".*/\\1/p' forge-output.json) && echo $ADDRESS && cast send --private-key 0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110 $ADDRESS --rpc-url http://localhost:8011 --value 0.1ether"
},
"dependsOn": ["build-contracts"]
},
"build:local": {
"executor": "nx:run-commands",
"options": {
Expand Down Expand Up @@ -60,7 +77,8 @@
"options": {
"cwd": "examples/demo-app",
"command": "pnpm exec playwright install chromium"
}
},
"dependsOn": ["deploy-contracts"]
},
"e2e": {
"executor": "nx:run-commands",
Expand Down
65 changes: 65 additions & 0 deletions examples/demo-app/smart-contracts/DemoPaymaster.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
/// !!! !!!
/// !!! THIS IS FOR DEMO PURPOSES ONLY !!!
/// !!! !!!
/// !!! DO NOT COPY THIS PAYMASTER !!!
/// !!! FOR PRODUCTION APPLICATIONS !!!
/// !!! !!!
/// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

import { IPaymaster, ExecutionResult, PAYMASTER_VALIDATION_SUCCESS_MAGIC } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymaster.sol";
import { IPaymasterFlow } from "@matterlabs/zksync-contracts/l2/system-contracts/interfaces/IPaymasterFlow.sol";
import { TransactionHelper, Transaction } from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/TransactionHelper.sol";

import "@matterlabs/zksync-contracts/l2/system-contracts/Constants.sol";

/// @author Matter Labs
/// @notice DO NOT USE THIS FOR PRODUCTION. This contract does not include any validations other than using the paymaster general flow.
contract DemoPaymaster is IPaymaster {
modifier onlyBootloader() {
require(msg.sender == BOOTLOADER_FORMAL_ADDRESS, "Only bootloader can call this method");
// Continue execution if called from the bootloader.
_;
}

function validateAndPayForPaymasterTransaction(
bytes32,
bytes32,
Transaction calldata _transaction
) external payable onlyBootloader returns (bytes4 magic, bytes memory context) {
// By default we consider the transaction as accepted.
magic = PAYMASTER_VALIDATION_SUCCESS_MAGIC;
require(_transaction.paymasterInput.length >= 4, "The standard paymaster input must be at least 4 bytes long");

bytes4 paymasterInputSelector = bytes4(_transaction.paymasterInput[0:4]);
require(paymasterInputSelector == IPaymasterFlow.general.selector, "Unsupported paymaster flow");

// Note, that while the minimal amount of ETH needed is tx.gasPrice * tx.gasLimit,
// neither paymaster nor account are allowed to access this context variable.
uint256 requiredETH = _transaction.gasLimit * _transaction.maxFeePerGas;

// The bootloader never returns any data, so it can safely be ignored here.
(bool success, ) = payable(BOOTLOADER_FORMAL_ADDRESS).call{ value: requiredETH }("");
require(success, "Failed to transfer tx fee to the Bootloader. Paymaster balance might not be enough.");
}

function postTransaction(
bytes calldata _context,
Transaction calldata _transaction,
bytes32,
bytes32,
ExecutionResult _txResult,
uint256 _maxRefundedGas
) external payable override onlyBootloader {}

function withdraw(address payable _to) external {
uint256 balance = address(this).balance;
(bool success, ) = _to.call{ value: balance }("");
require(success, "Failed to withdraw funds from paymaster.");
}

receive() external payable {}
}
Loading

0 comments on commit b9afdc3

Please sign in to comment.