Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: auth server and sdk when not using sessions #45

Merged
merged 6 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading