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

🔄 synced file(s) with circlefin/pw-sdk-nodejs-server-internal #6

Merged
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
1 change: 1 addition & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Located here https://console.circle.com/api-keys
API_KEY=[Paste API Key here]

PORT=8080
Expand Down
60 changes: 48 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# Circle User-Controlled Wallets Sample App - Backend Server

Check out the [live demo](http://sample-app.circle.com/pw-user-controlled/foundational) first to see what to expect!
Check out the [live demo](https://user-controlled-wallets-sample-app.circle.com/) first to see what to expect!

## Overview

User-Controlled Wallets Sample App showcases the integration of Circle's Web3 Services products (Web SDK, [Smart Contract Accounts (SCA)](https://developers.circle.com/w3s/docs/programmable-wallets-account-types) user-controlled wallets, gasless transactions). You can download and easily run and configure for your own projects. The use case it will be supporting is integrating user-controlled wallets into an existing web application, so that you can provide wallets to your end users.

This is a sample backend server that plays a part in the larger Sample App project. We use [Circle Web3 Services Node.js SDK](https://developers.circle.com/w3s/docs/nodejs-sdk) to interact with Circle Web3 Services APIs.
The backend server is a crucial component of the User-Controlled Wallets Sample App, responsible for communicating with the [Circle Web3 Services API](https://developers.circle.com/w3s/reference). It leverages the [Circle Web3 Services Node.js SDK](https://developers.circle.com/w3s/docs/nodejs-sdk) to enable the client-side application to interact with Circle's Web3 Services, such as user-controlled wallets, which can perform gasless transactions because they are [Smart Contract Accounts (SCA)](https://developers.circle.com/w3s/docs/programmable-wallets-account-types) linked to Circle's paymaster policy.

## Prerequisites

Expand All @@ -16,14 +14,16 @@ This is a sample backend server that plays a part in the larger Sample App proje

3. Install [nvm](https://github.com/nvm-sh/nvm) and [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable), these are required development tools.

4. **_Important:_** Set up [Sample App Frontend UI](https://github.com/circlefin/w3s-sample-user-controlled-client-web) as well to get the end-to-end experience. Please be aware that the [SDK user token](https://developers.circle.com/w3s/reference/getusertoken) will expire after 60 minutes.

## Configure the Sample App

1. Run `yarn env:config`, and you will see a `.env` file generated in the root directory.
2. Paste your API key into the `.env` file.
2. Paste your [API key](https://console.circle.com/api-keys) into the `.env` file.

## Get Started

Run the following commands to start the server with an in-memory SQLite database at `localhost:8080/pw-user-controlled/foundational`:
Run the following commands to start the server with an in-memory SQLite database at `localhost:8080`:

``` bash
nvm use
Expand All @@ -35,17 +35,53 @@ yarn dev
2. `yarn install`: install dependencies.
3. `yarn dev`: run the server, hot reload is supported.

Set up [Sample App Frontend UI](https://github.com/circlefin/w3s-sample-user-controlled-client-web) as well to get the end-to-end experience. Please be aware that the [SDK user token](https://developers.circle.com/w3s/reference/getusertoken) will expire after 60 minutes.

## Architecture

We use [Express](https://expressjs.com/) as web framework and [SQLite](https://www.sqlite.org/) as default database.

- The main logic to interact with Circle Web3 Services Node.js SDK is under `src/controllers`.

The backend server will play the role as `Your Server`, see [details](<https://developers.circle.com/w3s/docs/sdk-architecture-for-user-controlled-wallets#sdk-architecture>).
![image](https://files.readme.io/a2a1678-SDK_UserC_Wallets_Sequence__Detailed2x.png)

## Code Structure

We use [Express](https://expressjs.com/) as web framework and [SQLite](https://www.sqlite.org/) as default database.

- The main logic to interact with Circle Web3 Services Node.js SDK is under `src/controllers`:
- In `onboarding.ts`, we use the SDK to generate a user token for both our Sign Up and Sign In functions by calling the `createUserToken`:

```javascript
const tokenResponse = await circleUserSdk.createUserToken({
userId: newUserId
});
```

- Majority of files under `src/controllers` will require this user token to be passed within the header. For instance, creating a transaction with `circleUserSdk.createTransaction(...)` in `transactions.ts`, `req.headers` holds the token value and `req.body` holds all the parameters that the client can pass in as an object. Once authorized and configured from the client, the SDK uses Programmable Wallets to send on-chain transactions:

```javascript
const response = await circleUserSdk.createTransaction({
userToken: req.headers['token'] as string,
fee: feeConfig,
idempotencyKey: req.body.idempotencyKey,
refId: req.body.refId,
amounts: req.body.amounts,
destinationAddress: req.body.destinationAddress,
nftTokenIds: req.body.nftTokenIds,
tokenId: req.body.tokenId,
walletId: req.body.walletId
});
```

- Shared logic for the routers live in `src/middleware`:
- `auth.ts`: logic to parse and validate user token
- `errorHandler.ts`: logic to handle errors
- `validation.ts`: logic to handle incoming parameter type

- `src/services` holds external resources that the server needs:
- `userControlledWalletSdk.ts`: will initialize an instance of the user-controlled wallet sdk to be used by the controllers.
- `db/`: configures the Sqlite database for the user credentials.
- `src/app.ts` sets up the express router configurations and sets the base path and sub paths for the controllers. Imported by `src/index.ts` as the entry point of the server.
- The above are the most important files to get an understanding of this server. All other files are specific to this server and not crucial to using Circle Web3 Services Node.js SDK.

**Happy Coding!**

## Additional Resources

- [Circle Web3 Services Node.js SDK](https://developers.circle.com/w3s/docs/nodejs-sdk) supports User-Controlled Wallets, Developer-Controlled Wallets and Smart Contract Platform. See [Programmable Wallets](https://developers.circle.com/w3s/docs/circle-programmable-wallets-an-overview) and [Smart Contract Platform](https://developers.circle.com/w3s/docs/smart-contract-platform) to learn about these features and concepts.
Expand Down
24 changes: 9 additions & 15 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,18 @@ import {
} from './routers';

export const app: Express = express();
const parentRouter = express.Router();

app.use(cors());
app.use(express.json());

parentRouter.get('/', (_req: Request, res: Response) => {
app.get('/', (_req: Request, res: Response) => {
res.send('Sample Server');
});

/**
* Health check endpoint.
*/
parentRouter.get('/ping', (_req: Request, res: Response) => {
app.get('/ping', (_req: Request, res: Response) => {
res.status(200).send('pong');
});

Expand All @@ -47,7 +46,7 @@ parentRouter.get('/ping', (_req: Request, res: Response) => {
* encryptionKey: string - encryption key to use to execute challengeIds
* challengeId: string - used to initiate a challenge flow to setup PIN + Wallet
*/
parentRouter.post('/signup', validate(authenticationSchema), signUp);
app.post('/signup', validate(authenticationSchema), signUp);

/**
* POST - /signIn
Expand All @@ -67,20 +66,15 @@ parentRouter.post('/signup', validate(authenticationSchema), signUp);
* If user credentials wrong or don't exist:
* returns 404
*/
parentRouter.post('/signin', validate(authenticationSchema), signIn);
app.post('/signin', validate(authenticationSchema), signIn);

/*
* Add all sub paths to the parent router
* Add all sub paths
*/
parentRouter.use('/users', usersRouter, authUserRouter);
parentRouter.use('/tokens', tokensRouter);
parentRouter.use('/wallets', authMiddleware, walletsRouter);
parentRouter.use('/transactions', transactionsRouter, authTransRouter);

/*
* Add the parent router with ALL paths to the main app
*/
app.use('/pw-user-controlled/foundational', parentRouter);
app.use('/users', usersRouter, authUserRouter);
app.use('/tokens', tokensRouter);
app.use('/wallets', authMiddleware, walletsRouter);
app.use('/transactions', transactionsRouter, authTransRouter);

// Error handling
app.use(errorHandler);
1 change: 0 additions & 1 deletion src/controllers/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const createTransaction = async (

const response = await circleUserSdk.createTransaction({
userToken: req.headers['token'] as string,
// Yup validation in the middleware allows the spread of the req.body valid.
fee: feeConfig,
idempotencyKey: req.body.idempotencyKey,
refId: req.body.refId,
Expand Down
4 changes: 1 addition & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ initDB();

const port = process.env.PORT ?? 8080;
const server = app.listen(port, () => {
logger.info(
`Server is running at http://localhost:${port}/pw-user-controlled/foundational`
);
logger.info(`Server is running at http://localhost:${port}`);
});

process.on('SIGINT', function () {
Expand Down
Loading