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

Temporal changes #741

Open
wants to merge 18 commits into
base: dev
Choose a base branch
from
Open
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,5 @@ Brewfile.lock.json

# asdf
.tool-versions

var/**
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ Here is a quick peek at the send stack. Quickly jump to any of the submodules by
│   ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/daimo-expo-passkeys">daimo-expo-passkeys</a>
│   ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/eslint-config-custom">eslint-config-customs</a>
│   ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/playwright">playwright</a>
| ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/shovel">shovel</a>
│   ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/snaplet">snaplet</a>
| ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/temporal">temporal</a>
│   ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/ui">ui</a>
│   ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/wagmi">wagmi</a>
│   └── <a href="https://github.com/0xsend/sendapp/tree/main/packages/webauthn-authenticator">webauthn-authenticator</a>
| ├── <a href="https://github.com/0xsend/sendapp/tree/main/packages/webauthn-authenticator">webauthn-authenticator</a>
│   └── <a href="https://github.com/0xsend/sendapp/tree/main/packages/workflows">workflows</a>
└── <a href="https://github.com/0xsend/sendapp/tree/main/supabase">supabase</a>
</code>
</pre>
Expand Down
62 changes: 62 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Makefile

# Check if .env.local exists, if not, create it from template
$(shell test -f .env.local || cp .env.local.template .env.local)
include .env.local

# Export variables from .env.local if not already set in the environment
define read_env
$(eval export $(shell sed -ne 's/ *#.*$$//; /./ s/=.*$$//; s/^/export /; s/$$/?=$$\(shell grep -m1 "^&=" .env.local | cut -d= -f2-\)/' .env.local))
endef

$(call read_env)

IMAGE_NAME = sendapp/next-app
GIT_BRANCH = $(shell git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD)
GIT_HASH = $(shell git rev-parse --short=10 HEAD)
NEXT_COMPOSE_IMAGE = "${IMAGE_NAME}-${GIT_BRANCH}-${GIT_HASH}"
DOCKERFILE_PATH = ./apps/next/Dockerfile
BUILD_CONTEXT = .

# Docker build arguments
BUILD_ARGS = \
--build-arg CI=${CI} \
--build-arg DEBUG=${DEBUG} \
--build-arg NEXT_PUBLIC_SUPABASE_PROJECT_ID=${NEXT_PUBLIC_SUPABASE_PROJECT_ID} \
--build-arg NEXT_PUBLIC_URL=${NEXT_PUBLIC_URL} \
--build-arg NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} \
--build-arg NEXT_PUBLIC_SUPABASE_GRAPHQL_URL=${NEXT_PUBLIC_SUPABASE_GRAPHQL_URL} \
--build-arg NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} \
--build-arg NEXT_PUBLIC_MAINNET_RPC_URL=${NEXT_PUBLIC_MAINNET_RPC_URL} \
--build-arg NEXT_PUBLIC_BASE_RPC_URL=${NEXT_PUBLIC_BASE_RPC_URL} \
--build-arg NEXT_PUBLIC_BUNDLER_RPC_URL=${NEXT_PUBLIC_BUNDLER_RPC_URL} \
--build-arg NEXT_PUBLIC_MAINNET_CHAIN_ID=${NEXT_PUBLIC_MAINNET_CHAIN_ID} \
--build-arg NEXT_PUBLIC_BASE_CHAIN_ID=${NEXT_PUBLIC_BASE_CHAIN_ID} \
--build-arg NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=${NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID} \
--build-arg NEXT_PUBLIC_TURNSTILE_SITE_KEY=${NEXT_PUBLIC_TURNSTILE_SITE_KEY}

# Docker secrets
SECRETS = \
--secret id=SUPABASE_DB_URL \
--secret id=SUPABASE_SERVICE_ROLE \
--secret id=TURBO_TOKEN \
--secret id=TURBO_TEAM


# Targets
.PHONY: docker-web docker-web-push
docker-web:
@if [ -z "$$(docker images -q $(NEXT_COMPOSE_IMAGE))" ]; then \
echo "Image $(NEXT_COMPOSE_IMAGE) does not exist locally. Building..."; \
docker buildx build --progress=plain --platform linux/amd64 -t $(IMAGE_NAME)-$(GIT_BRANCH):$(GIT_HASH) -t $(IMAGE_NAME)-$(GIT_BRANCH):latest $(BUILD_ARGS) $(SECRETS) -f $(DOCKERFILE_PATH) $(BUILD_CONTEXT) ;\
else \
echo "Image $(NEXT_COMPOSE_IMAGE) already exists locally. Skipping build."; \
fi

docker-web-push: docker-web
docker push $(IMAGE_NAME)-$(GIT_BRANCH):$(GIT_HASH)
docker push $(IMAGE_NAME)-$(GIT_BRANCH):latest

# Prune docker system images and containers older than 7 days != otterscan
docker-clean:
docker image prune -f --filter "label!=otterscan*" --filter until=168h
1 change: 1 addition & 0 deletions apps/next/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ COPY packages/eslint-config-custom/package.json packages/eslint-config-custom/pa
COPY packages/playwright/package.json packages/playwright/package.json
COPY packages/shovel/package.json packages/shovel/package.json
COPY packages/snaplet/package.json packages/snaplet/package.json
COPY packages/temporal/package.json packages/temporal/package.json
COPY packages/ui/package.json packages/ui/package.json
COPY packages/wagmi/package.json packages/wagmi/package.json
COPY packages/webauthn-authenticator/package.json packages/webauthn-authenticator/package.json
Expand Down
3 changes: 2 additions & 1 deletion apps/next/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"pathToApp": "."
}
],
"types": ["node"]
"types": ["node"],
"sourceMap": true
},
"include": [
"next-env.d.ts",
Expand Down
7 changes: 5 additions & 2 deletions apps/workers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@
],
"scripts": {
"lint": "tsc",
"start": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' src/worker.ts",
"workflow": "node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' src/client.ts"
"start": "yarn with-env node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' src/worker.ts",
"workflow": "yarn with-env node --import 'data:text/javascript,import { register } from \"node:module\"; import { pathToFileURL } from \"node:url\"; register(\"ts-node/esm\", pathToFileURL(\"./\"));' src/client.ts",
"with-env": "dotenv -e ../../.env -c --"
},
"devDependencies": {
"@types/bun": "^1.1.6",
"dotenv-cli": "^7.3.0",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"
},
"peerDependencies": {
"typescript": "^5.5.3"
},
"dependencies": {
"@my/temporal": "workspace:*",
"@my/workflows": "workspace:*",
"@temporalio/worker": "^1.10.1"
}
Expand Down
72 changes: 40 additions & 32 deletions apps/workers/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,51 @@
import { Connection, Client } from '@temporalio/client'
import {
// WorkflowA, WorkflowB,

DistributionsWorkflow,
} from '@my/workflows/all-workflows'
import { SendTransferWorkflow } from '@my/workflows'
import type { UserOperation } from 'permissionless'

export async function runWorkflow(): Promise<void> {
const connection = await Connection.connect() // Connect to localhost with default ConnectionOptions.
// In production, pass options to the Connection constructor to configure TLS and other settings.
// This is optional but we leave this here to remind you there is a gRPC connection being established.
// async function runDistributionWorkflow() {
// const connection = await Connection.connect() // Connect to localhost with default ConnectionOptions.
// // In production, pass options to the Connection constructor to configure TLS and other settings.
// // This is optional but we leave this here to remind you there is a gRPC connection being established.

// const client = new Client({
// connection,
// // In production you will likely specify `namespace` here; it is 'default' if omitted
// })

// // Invoke the `DistributionWorkflow` Workflow, only resolved when the workflow completes
// const handle = await client.workflow.start(DistributionsWorkflow, {
// taskQueue: 'dev',
// workflowId: 'distributions-workflow', // TODO: remember to replace this with a meaningful business ID
// args: [], // type inference works! args: [name: string]
// })
// console.log('Started handle', handle.workflowId)
// // optional: wait for client result
// const result = await handle.result()

// return result
// }

export async function runTransferWorkflow(userOp: UserOperation<'v0.7'>) {
const connection = await Connection.connect()
const client = new Client({
connection,
// In production you will likely specify `namespace` here; it is 'default' if omitted
})

// Invoke the `DistributionWorkflow` Workflow, only resolved when the workflow completes
const handle = await client.workflow.start(DistributionsWorkflow, {
taskQueue: 'dev',
workflowId: 'distributions-workflow', // TODO: remember to replace this with a meaningful business ID
args: [], // type inference works! args: [name: string]
const handle = await client.workflow.start(SendTransferWorkflow, {
taskQueue: 'monorepo',
workflowId: `transfers-workflow-${userOp.sender}-${userOp.nonce.toString()}`, // TODO: remember to replace this with a meaningful business ID
args: [userOp],
})
console.log('Started handle', handle)
// // Invoke the `WorkflowA` Workflow, only resolved when the workflow completes
// const result = await client.workflow.execute(WorkflowA, {
// taskQueue: 'monorepo',
// workflowId: `workflow-a-${Date.now()}`, // TODO: remember to replace this with a meaningful business ID
// args: ['Temporal'], // type inference works! args: [name: string]
// })
// // Starts the `WorkflowB` Workflow, don't wait for it to complete
// await client.workflow.start(WorkflowB, {
// taskQueue: 'monorepo',
// workflowId: `workflow-b-${Date.now()}`, // TODO: remember to replace this with a meaningful business ID
// })
// console.log(result) // // [api-server] A: Hello, Temporal!, B: Hello, Temporal!
// return result
console.log('Started handle', handle.workflowId)
// optional: wait for client result
const result = await handle.result()
console.log('result: ', result)

return result
}

runWorkflow().catch((err) => {
console.error(err)
process.exit(1)
})
// runDistributionWorkflow().catch((err) => {
// console.error(err)
// process.exit(1)
// })
63 changes: 38 additions & 25 deletions apps/workers/src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,51 @@
import { Worker } from '@temporalio/worker'
import { createActivities } from '@my/workflows/all-activities'
import { URL, fileURLToPath } from 'node:url'
import path from 'node:path'
import { Worker, NativeConnection } from '@temporalio/worker'
import {
createTransferActivities,
createDistributionActivities,
} from '@my/workflows/all-activities'
import fs from 'node:fs/promises'
import { createRequire } from 'node:module'
import { dataConverter } from '@my/temporal/payload-converter'
const require = createRequire(import.meta.url)

const { NODE_ENV = 'development' } = process.env
const isDeployed = ['production', 'staging'].includes(NODE_ENV)

async function run() {
const workflowsPathUrl = new URL(
`../../../packages/workflows/src/all-workflows${path.extname(import.meta.url)}`,
import.meta.url
)
const connection = isDeployed
? await NativeConnection.connect({
address: `${process.env.TEMPORAL_NAMESPACE}.tmprl.cloud:7233`,
tls: {
clientCertPair: {
crt: await fs.readFile(process.env.TEMPORAL_MTLS_TLS_CERT ?? '').catch((e) => {
console.error(e)
throw new Error('no cert found. Check the TEMPORAL_MTLS_TLS_CERT env var')
}),
key: await fs.readFile(process.env.TEMPORAL_MTLS_TLS_KEY ?? '').catch((e) => {
console.error(e)
throw new Error('no key found. Check the TEMPORAL_MTLS_TLS_KEY env var')
}),
},
},
})
: undefined

// Step 1: Register Workflows and Activities with the Worker and connect to
// the Temporal server.
const worker = await Worker.create({
workflowsPath: fileURLToPath(workflowsPathUrl),
activities: createActivities(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.SUPABASE_SERVICE_ROLE
),
taskQueue: 'dev',
connection,
dataConverter: dataConverter,
workflowsPath: require.resolve('@my/workflows'),
activities: {
...createTransferActivities(process.env),
...createDistributionActivities(process.env),
},
namespace: process.env.TEMPORAL_NAMESPACE ?? 'default',
taskQueue: 'monorepo',
bundlerOptions: {
ignoreModules: ['@supabase/supabase-js'],
},
})
// Worker connects to localhost by default and uses console.error for logging.
// Customize the Worker by passing more options to create():
// https://typescript.temporal.io/api/classes/worker.Worker

// If you need to configure server connection parameters, see the mTLS example:
// https://github.com/temporalio/samples-typescript/tree/main/hello-world-mtls

// Step 2: Start accepting tasks on the `monorepo` queue
await worker.run()

// You may create multiple Workers in a single process in order to poll on multiple task queues.
}

run().catch((err) => {
Expand Down
7 changes: 5 additions & 2 deletions apps/workers/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
"composite": true,
"baseUrl": ".",
"paths": {
"@my/workflows": ["../../packages/workflows/src/*"],
"@my/workflows/*": ["../../packages/workflows/src/*"]
"@my/workflows": ["../../packages/workflows/src/all-workflows.ts"],
"@my/workflows/*": ["../../packages/workflows/src/*"],
"@my/temporal": ["../../packages/temporal/src"],
"@my/temporal/*": ["../../packages/temporal/src/*"]
}
},
"references": [],
"include": [
"./src",
"../../packages/workflows/src",
"../../packages/temporal/src",
"../../globals.d.ts",
"../../environment.d.ts"
]
Expand Down
4 changes: 3 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
"./packages/app/components/img/**",
"packages/shovel/etc/config.json",
"./supabase/.temp/**",
"./packages/contracts/var/*.json"
"./packages/contracts/var/*.json",
"**/tsconfig.json",
"**/*.tsconfig.json"
]
},
"organizeImports": {
Expand Down
7 changes: 7 additions & 0 deletions environment.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ declare global {
NEXT_PUBLIC_SUPABASE_PROJECT_ID: string
NEXT_PUBLIC_SUPABASE_GRAPHQL_URL: string
NEXT_PUBLIC_MAINNET_RPC_URL: string
/**
* The URL of the ERC 4337 Account Abstraction Bundler RPC endpoint
*/
BUNDLER_RPC_URL: string
/**
* The URL of the ERC 4337 Account Abstraction Bundler RPC endpoint
*/
NEXT_PUBLIC_BASE_RPC_URL: string
NEXT_PUBLIC_BUNDLER_RPC_URL: string
SUPABASE_DB_URL: string
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
"playwright": "yarn workspace @my/playwright",
"distributor": "yarn workspace distributor",
"snaplet": "yarn workspace @my/snaplet",
"workers": "yarn workspace workers",
"shovel": "yarn workspace @my/shovel",
"temporal": "yarn workspace @my/temporal",
"clean": "yarn workspaces foreach --all -pi run clean"
},
"resolutions": {
Expand All @@ -55,7 +57,7 @@
"check-dependency-version-consistency": "^3.0.3",
"eslint": "^8.46.0",
"node-gyp": "^9.3.1",
"turbo": "^2.0.3",
"turbo": "^2.1.2",
"typescript": "^5.5.3"
},
"packageManager": "[email protected]",
Expand Down
4 changes: 3 additions & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"private": true,
"dependencies": {
"@my/supabase": "workspace:*",
"@my/temporal": "workspace:*",
"@my/wagmi": "workspace:*",
"@my/workflows": "workspace:*",
"@supabase/supabase-js": "2.44.2",
"@tanstack/react-query": "^5.18.1",
"@trpc/client": "11.0.0-next-beta.264",
Expand All @@ -17,7 +19,7 @@
"debug": "^4.3.6",
"ms": "^2.1.3",
"p-queue": "^8.0.1",
"superjson": "^1.13.1",
"superjson": "^2.2.1",
"viem": "^2.18.2",
"zod": "^3.23.8"
}
Expand Down
2 changes: 2 additions & 0 deletions packages/api/src/routers/_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { distributionRouter } from './distribution'
import { tagRouter } from './tag'
import { secretShopRouter } from './secretShop'
import { sendAccountRouter } from './sendAccount'
import { transferRouter } from './transfer'
import { accountRecoveryRouter } from './account-recovery/router'
import { referralsRouter } from './referrals'

Expand All @@ -17,6 +18,7 @@ export const appRouter = createTRPCRouter({
distribution: distributionRouter,
secretShop: secretShopRouter,
sendAccount: sendAccountRouter,
transfer: transferRouter,
referrals: referralsRouter,
})

Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/routers/account-recovery/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const accountRecoveryRouter = createTRPCRouter({
.single()

if (challengeError || !challengeData) {
logger(`getChallenge:cant-insert-challenge: [${challengeError}]`)
logger(`getChallenge:cant-insert-challenge: [${JSON.stringify(challengeError)}]`)
throw new TRPCError({
code: 'INTERNAL_SERVER_ERROR',
message: formatErr('Cannot generate challenge: Internal server error'),
Expand Down
Loading
Loading