Skip to content

Commit

Permalink
🔄 synced file(s) with circlefin/pw-sdk-nodejs-server-internal (#6)
Browse files Browse the repository at this point in the history
synced local file(s) with
[circlefin/pw-sdk-nodejs-server-internal](https://github.com/circlefin/pw-sdk-nodejs-server-internal).



<details>
<summary>Changed files</summary>
<ul>
<li>synced local <code>README.md</code> with remote
<code>README.md</code></li><li>synced local directory <code>src/</code>
with remote directory <code>src/</code></li><li>synced local
<code>.env.sample</code> with remote <code>.env.sample</code></li>
</ul>
</details>

---

This PR was created automatically by the
[repo-file-sync-action](https://github.com/BetaHuhn/repo-file-sync-action)
workflow run
[#8928338085](https://github.com/circlefin/pw-sdk-nodejs-server-internal/actions/runs/8928338085)
  • Loading branch information
alex-chiu-circle authored May 3, 2024
2 parents 60cdae4 + 3805be4 commit 0d01fe0
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 31 deletions.
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

0 comments on commit 0d01fe0

Please sign in to comment.