From 2beec28ccb5dcf2dcd98ab1efb31fa961174ac21 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Thu, 26 Jun 2025 12:46:55 +0300 Subject: [PATCH 1/7] Added Privy & Sequence guide --- docs.json | 13 +- sdk/headless-wallet/use-with-privy.mdx | 561 +++++++++++++++++++++++++ 2 files changed, 571 insertions(+), 3 deletions(-) create mode 100644 sdk/headless-wallet/use-with-privy.mdx diff --git a/docs.json b/docs.json index 27e335cd..6123e4b4 100644 --- a/docs.json +++ b/docs.json @@ -446,7 +446,8 @@ "sdk/headless-wallet/on-ramp", "sdk/headless-wallet/fee-options", "sdk/headless-wallet/verification", - "sdk/headless-wallet/transaction-receipts" + "sdk/headless-wallet/transaction-receipts", + "sdk/headless-wallet/use-with-privy" ] } ] @@ -1230,7 +1231,8 @@ "ja/sdk/headless-wallet/on-ramp", "ja/sdk/headless-wallet/fee-options", "ja/sdk/headless-wallet/verification", - "ja/sdk/headless-wallet/transaction-receipts" + "ja/sdk/headless-wallet/transaction-receipts", + "ja/sdk/headless-wallet/use-with-privy" ] } ] @@ -2031,7 +2033,8 @@ "es/sdk/headless-wallet/on-ramp", "es/sdk/headless-wallet/fee-options", "es/sdk/headless-wallet/verification", - "es/sdk/headless-wallet/transaction-receipts" + "es/sdk/headless-wallet/transaction-receipts", + "es/sdk/headless-wallet/use-with-privy" ] } ] @@ -2568,6 +2571,10 @@ { "source": "/solutions/builder/overview", "destination": "/solutions/getting-started" + }, + { + "source": "/solutions/wallets/embedded-wallet/examples/use-with-privy", + "destination": "/sdk/headless-wallet/use-with-privy" } ], "theme": "mint" diff --git a/sdk/headless-wallet/use-with-privy.mdx b/sdk/headless-wallet/use-with-privy.mdx new file mode 100644 index 00000000..91ef1552 --- /dev/null +++ b/sdk/headless-wallet/use-with-privy.mdx @@ -0,0 +1,561 @@ +--- +title: "Privy & Sequence" +description: "Learn how to use Privy as a signer for your Sequence Smart Wallet." +--- + +In this guide, you'll learn how to use the core `sequence.js` libraries to seamlessly integrate Privy with Sequence, enabling your users to sign in and interact with your dApp through a Sequence Smart Wallet. This involves creating a Sequence wallet controlled by a user's Privy-managed EOA, and then using that Sequence wallet to send gasless transactions on Base Sepolia. + + +We will be using Next.js 15, React 19, and Tailwind CSS 4. + + + + + +You'll need the Sequence, Privy, and wagmi/viem packages. + +```bash +pnpm install @0xsequence/account @0xsequence/core @0xsequence/network @0xsequence/sessions @0xsequence/signhub @privy-io/react-auth @privy-io/wagmi-connector wagmi @privy-io/wagmi @tanstack/react-query viem ethers +``` + + + +You'll need to get a Privy App ID and Client ID. You can get these by creating a new app in the [Privy Dashboard](https://dashboard.privy.io/apps). +Create an `.env.local` file in your project root and add your `NEXT_PUBLIC_PRIVY_APP_ID` and `NEXT_PUBLIC_PRIVY_CLIENT_ID`. + + + +Set up `WagmiProvider` and `PrivyProvider` in your a `providers.tsx` file. +We do this to allow our app to use both Privy for authentication and wagmi for wallet interactions. + +```tsx [providers.tsx] +'use client' + +import { type PrivyClientConfig, PrivyProvider } from '@privy-io/react-auth' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { createConfig, WagmiProvider } from '@privy-io/wagmi' +import { baseSepolia } from 'viem/chains' +import { http } from 'wagmi' + +const queryClient = new QueryClient() + +const wagmiConfig = createConfig({ + chains: [baseSepolia], + transports: { + [baseSepolia.id]: http() + } +}) + +const privyConfig: PrivyClientConfig = { + embeddedWallets: { + requireUserPasswordOnCreate: true, + showWalletUIs: true + }, + loginMethods: ['wallet', 'email', 'google'], + appearance: { + showWalletLoginFirst: true + }, + defaultChain: baseSepolia +} + +const APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID +const CLIENT_ID = process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID + +export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ) +} +``` + + + +Wrap the App's layout with the Providers. + +```tsx [layout.tsx] +import type { Metadata } from 'next' +import { Geist, Geist_Mono } from 'next/font/google' +import './globals.css' +import Providers from './providers' + +const geistSans = Geist({ + variable: '--font-geist-sans', + subsets: ['latin'] +}) + +const geistMono = Geist_Mono({ + variable: '--font-geist-mono', + subsets: ['latin'] +}) + +export const metadata: Metadata = { + title: 'Privy + Sequence', + description: 'A demo showcasing how Sequence can be used with Privy' +} + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + ) +} +``` + + + +Add the following types to `./constants/types.ts`. + +```tsx [./constants/types.ts] +export type FlatTransaction = { + to: string + value?: string + data?: string + gasLimit?: string + delegateCall?: boolean + revertOnError?: boolean +} + +export type TransactionsEntry = { + subdigest?: string + wallet: string + space: string + nonce: string + chainId: string + transactions: FlatTransaction[] +} +``` + + + +Create a `StaticSigner` class in `./utils/StaticSigner.ts`. + +```tsx [./utils/StaticSigner.ts] +import type { commons } from '@0xsequence/core' +import type { signers } from '@0xsequence/signhub' +import { type BytesLike, ethers } from 'ethers' + +type TransactionBundle = commons.transaction.TransactionBundle +type SignedTransactionBundle = commons.transaction.SignedTransactionBundle +type IntendedTransactionBundle = commons.transaction.IntendedTransactionBundle + +export class StaticSigner implements signers.SapientSigner { + private readonly signatureBytes: Uint8Array + private readonly savedSuffix: Uint8Array + + constructor( + private readonly address: string, + private readonly signature: string + ) { + const raw = ethers.getBytes(this.signature) + + // Separate last byte as suffix + this.savedSuffix = raw.slice(-1) + this.signatureBytes = raw.slice(0, -1) + } + + async buildDeployTransaction(): Promise { + return undefined + } + + async predecorateSignedTransactions(): Promise { + return [] + } + + async decorateTransactions( + og: IntendedTransactionBundle + ): Promise { + return og + } + + async sign(): Promise { + return this.signatureBytes + } + + notifyStatusChange(): void {} + + suffix(): BytesLike { + return this.savedSuffix + } + + async getAddress() { + return this.address + } +} +``` + + + +We need a couple of utility methods. + +Add this file in `./utils/index.ts`. + +```tsx [index.ts] +import { Account } from '@0xsequence/account' +import { trackers } from '@0xsequence/sessions' +import { commons } from '@0xsequence/core' +import { Orchestrator, signers } from '@0xsequence/signhub' +import { allNetworks } from '@0xsequence/network' +import type { FlatTransaction, TransactionsEntry } from '../constants/types' +import { ethers } from 'ethers' +import { StaticSigner } from './StaticSigner' + +export const TRACKER = new trackers.remote.RemoteConfigTracker( + 'https://sessions.sequence.app' +) + +export const NETWORKS = allNetworks + +/** + * Creates a new Sequence Account with the specified threshold and signers. + * + * @param threshold - The minimum weight required to authorize transactions. + * @param signers - An array of signer objects with address and weight. + * @returns A Promise that resolves to the created Account instance. + */ +export async function createSequenceAccount( + threshold: number, + signers: { address: string; weight: number }[] +): Promise { + const account = await Account.new({ + config: { + threshold, + // By default a random checkpoint is generated every second + checkpoint: 0, + signers: signers + }, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator([]), + networks: NETWORKS + }) + + return account +} + +/** + * Converts an array of FlatTransaction objects to Sequence Transaction objects. + * + * @param txs - Array of FlatTransaction objects to convert. + * @returns An array of Sequence Transaction objects. + */ +export function toSequenceTransactions( + txs: FlatTransaction[] +): commons.transaction.Transaction[] { + return txs.map(toSequenceTransaction) +} + +/** + * Converts a FlatTransaction object to a Sequence Transaction object. + * + * @param tx - The FlatTransaction object to convert. + * @returns The corresponding Sequence Transaction object. + */ +export function toSequenceTransaction( + tx: FlatTransaction +): commons.transaction.Transaction { + return { + to: tx.to, + value: tx.value ? BigInt(tx.value) : undefined, + data: tx.data, + gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : undefined, + delegateCall: tx.delegateCall || false, + revertOnError: tx.revertOnError || false + } +} + +/** + * Creates an Account instance for a given address and optional signatures. + * + * @param args - Object containing the address and optional signatures array. + * @returns An Account instance configured with the provided signers. + */ +export function accountFor(args: { + address: string + signatures?: { signer: string; signature: string }[] +}) { + const signers: signers.SapientSigner[] = [] + + if (args.signatures) { + for (const { signer, signature } of args.signatures) { + const signatureArr = ethers.getBytes(signature) + if ( + signatureArr.length === 66 && + (signatureArr[64] === 0 || signatureArr[64] === 1) + ) { + signatureArr[64] = signatureArr[64] + 27 + } + + signers.push(new StaticSigner(signer, ethers.hexlify(signatureArr))) + } + } + + return new Account({ + address: args.address, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator(signers), + networks: NETWORKS + }) +} + +/** + * Computes the digest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The digest string for the transactions. + */ +export function digestOf(tx: TransactionsEntry): string { + return commons.transaction.digestOfTransactions( + commons.transaction.encodeNonce(tx.space, tx.nonce), + toSequenceTransactions(tx.transactions) + ) +} + +/** + * Computes the subdigest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The subdigest string for the transactions. + */ +export function subdigestOf(tx: TransactionsEntry): string { + const digest = digestOf(tx) + + return commons.signature.subdigestOf({ + digest, + chainId: tx.chainId, + address: tx.wallet + }) +} + +/** + * Converts Sequence Transactionish objects to an array of FlatTransaction objects. + * + * @param wallet - The wallet address associated with the transactions. + * @param txs - The Sequence Transactionish object(s) to convert. + * @returns An array of FlatTransaction objects. + */ +export function fromSequenceTransactions( + wallet: string, + txs: commons.transaction.Transactionish +): FlatTransaction[] { + const sequenceTxs = commons.transaction.fromTransactionish(wallet, txs) + return sequenceTxs.map((stx) => ({ + to: stx.to, + value: stx.value?.toString(), + data: stx.data?.toString(), + gasLimit: stx.gasLimit?.toString(), + delegateCall: stx.delegateCall, + revertOnError: stx.revertOnError + })) +} + +/** + * Recovers the signer addresses from an array of signatures and a subdigest. + * + * @param signatures - Array of signature strings to recover signers from. + * @param subdigest - The subdigest string used for recovery. + * @returns An array of objects containing the signer address and signature. + */ +export function recoverSigner( + signatures: string[], + subdigest: string +): { signer: string; signature: string }[] { + const res: { signer: string; signature: string }[] = [] + + for (const signature of signatures) { + try { + const r = commons.signer.recoverSigner(subdigest, signature) + res.push({ signer: r, signature: signature }) + } catch (e) { + console.error('Failed to recover signature', e) + } + } + + return res +} +``` + + + + +```tsx [page.tsx] +"use client" + +import { usePublicClient, useSignMessage } from "wagmi" +import { accountFor, createSequenceAccount, subdigestOf, toSequenceTransactions } from "./utils" +import { useState, useEffect } from "react" +import { commons } from "@0xsequence/core" +import { ethers } from "ethers" +import { zeroAddress } from "viem" +import { usePrivy } from "@privy-io/react-auth" + +const CHAIN_ID = 84532 + +export default function Home() { + const { ready, authenticated, login, logout, user } = usePrivy() + const publicClient = usePublicClient({ chainId: CHAIN_ID }) + const { signMessageAsync } = useSignMessage() + const [walletAddress, setWalletAddress] = useState<`0x${string}` | null>(null) + const [txHash, setTxHash] = useState(null) + const [loadingSendTx, setLoadingSendTx] = useState(false) + const [isWalletDeployed, setIsWalletDeployed] = useState(false) + const [checkingWalletDeployed, setCheckingWalletDeployed] = useState(true) + + useEffect(() => { + const createWallet = async () => { + if (user?.wallet && user.wallet.address) { + const seqeunceAccount = await createSequenceAccount(1, [ + { address: user.wallet.address, weight: 1 }, + ]) + const accountWithSig = accountFor({ + address: seqeunceAccount.address, + }) + const status = await accountWithSig.status(CHAIN_ID) + const wallet = accountWithSig.walletForStatus(CHAIN_ID, status) + setCheckingWalletDeployed(true) + const hasCode = await publicClient?.getCode({ address: accountWithSig.address as `0x${string}` }) + setCheckingWalletDeployed(false) + if (!hasCode) { + wallet.deploy() + // Wait for the wallet to be deploy, most of the times it takes less than 4 seconds + await new Promise((resolve) => setTimeout(resolve, 4000)) + } + setWalletAddress(wallet.address as `0x${string}`) + setIsWalletDeployed(true) + setCheckingWalletDeployed(false) + } else { + setWalletAddress(null) + setTxHash(null) + } + } + createWallet() + }, [user]) + + const handleSend = async () => { + if (!user?.wallet?.address || !walletAddress) return + setLoadingSendTx(true) + const txs = [ + { to: zeroAddress, data: "0x", value: "0", revertOnError: true }, + ] + const txe = { + wallet: walletAddress, + space: Date.now().toString(), + nonce: "0", + chainId: CHAIN_ID.toString(), + transactions: txs, + } + const subdigest = subdigestOf(txe) + const digestBytes = ethers.getBytes(subdigest) + const signature = await signMessageAsync({ message: { raw: digestBytes } }) + const suffixed = signature + "02" + const account = accountFor({ + address: walletAddress, + signatures: [ + { signer: user.wallet.address as `0x${string}`, signature: suffixed }, + ], + }) + const sequenceTxs = toSequenceTransactions(txs) + const status = await account.status(CHAIN_ID) + const wallet = account.walletForStatus(CHAIN_ID, status) + const signed = await wallet.signTransactions( + sequenceTxs, + commons.transaction.encodeNonce(txe.space, txe.nonce) + ) + const relayer = account.relayer(CHAIN_ID) + const relayed = await relayer.relay(signed) + setTxHash(relayed?.hash || null) + setLoadingSendTx(false) + } + + if (!ready) + return ( +
+ Loading Privy... +
+ ) + + return ( +
+ + {isWalletDeployed ? ( +
+
Smart Wallet Address
+
{walletAddress}
+
+ ) : ( +
+ {checkingWalletDeployed ? ( +
Checking if wallet is deployed...
+ ) : ( +
Deploying Sequence Smart Wallet...
+ )} +
+ )} + {walletAddress && ( + + )} + {txHash && ( +
+
Transaction Hash
+
{txHash}
+
+ )} +
+ ) +} +``` +
+ + + +```bash +pnpm dev +``` + +
\ No newline at end of file From e19c2462e5c744b7fa4fa4f16b2416d5116f9053 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Jun 2025 09:48:29 +0000 Subject: [PATCH 2/7] chore(i18n): update translations [en] Sync file structure, format locales. Branch: 136/merge --- es/sdk/headless-wallet/use-with-privy.mdx | 558 ++++++++++++++++++++++ ja/sdk/headless-wallet/use-with-privy.mdx | 558 ++++++++++++++++++++++ 2 files changed, 1116 insertions(+) create mode 100644 es/sdk/headless-wallet/use-with-privy.mdx create mode 100644 ja/sdk/headless-wallet/use-with-privy.mdx diff --git a/es/sdk/headless-wallet/use-with-privy.mdx b/es/sdk/headless-wallet/use-with-privy.mdx new file mode 100644 index 00000000..c97faa50 --- /dev/null +++ b/es/sdk/headless-wallet/use-with-privy.mdx @@ -0,0 +1,558 @@ +--- +title: Privy y Sequence +description: Aprenda cómo usar Privy como firmante para su Sequence Smart Wallet. +--- + +En esta guía, aprenderá a utilizar las bibliotecas principales de `sequence.js` para integrar Privy con Sequence de manera sencilla, permitiendo que sus usuarios inicien sesión e interactúen con su dApp a través de una Sequence Smart Wallet. Esto implica crear una wallet de Sequence controlada por la EOA gestionada por Privy del usuario, y luego usar esa wallet de Sequence para enviar transacciones sin gas en Base Sepolia. + + + Usaremos Next.js 15, React 19 y Tailwind CSS 4. + + + + + Necesitará los paquetes de Sequence, Privy y wagmi/viem. + + ```bash + pnpm install @0xsequence/account @0xsequence/core @0xsequence/network @0xsequence/sessions @0xsequence/signhub @privy-io/react-auth @privy-io/wagmi-connector wagmi @privy-io/wagmi @tanstack/react-query viem ethers + ``` + + + + Necesitará obtener un Privy App ID y un Client ID. Puede conseguirlos creando una nueva aplicación en el [Privy Dashboard](https://dashboard.privy.io/apps). + Cree un archivo `.env.local` en la raíz de su proyecto y agregue sus valores de `NEXT_PUBLIC_PRIVY_APP_ID` y `NEXT_PUBLIC_PRIVY_CLIENT_ID`. + + + + Configure `WagmiProvider` y `PrivyProvider` en un archivo `providers.tsx`. + Esto nos permite que la aplicación use tanto Privy para autenticación como wagmi para interacciones con wallets. + + ```tsx [providers.tsx] + 'use client' + + import { type PrivyClientConfig, PrivyProvider } from '@privy-io/react-auth' + import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + import { createConfig, WagmiProvider } from '@privy-io/wagmi' + import { baseSepolia } from 'viem/chains' + import { http } from 'wagmi' + + const queryClient = new QueryClient() + + const wagmiConfig = createConfig({ + chains: [baseSepolia], + transports: { + [baseSepolia.id]: http() + } + }) + + const privyConfig: PrivyClientConfig = { + embeddedWallets: { + requireUserPasswordOnCreate: true, + showWalletUIs: true + }, + loginMethods: ['wallet', 'email', 'google'], + appearance: { + showWalletLoginFirst: true + }, + defaultChain: baseSepolia + } + + const APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID + const CLIENT_ID = process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID + + export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ) + } + ``` + + + + Envuelva el layout de la App con los Providers. + + ```tsx [layout.tsx] + import type { Metadata } from 'next' + import { Geist, Geist_Mono } from 'next/font/google' + import './globals.css' + import Providers from './providers' + + const geistSans = Geist({ + variable: '--font-geist-sans', + subsets: ['latin'] + }) + + const geistMono = Geist_Mono({ + variable: '--font-geist-mono', + subsets: ['latin'] + }) + + export const metadata: Metadata = { + title: 'Privy + Sequence', + description: 'A demo showcasing how Sequence can be used with Privy' + } + + export default function RootLayout({ + children + }: Readonly<{ + children: React.ReactNode + }>) { + return ( + + + {children} + + + ) + } + ``` + + + + Agregue los siguientes tipos en `./constants/types.ts`. + + ```tsx [./constants/types.ts] + export type FlatTransaction = { + to: string + value?: string + data?: string + gasLimit?: string + delegateCall?: boolean + revertOnError?: boolean + } + + export type TransactionsEntry = { + subdigest?: string + wallet: string + space: string + nonce: string + chainId: string + transactions: FlatTransaction[] + } + ``` + + + + Cree una clase `StaticSigner` en `./utils/StaticSigner.ts`. + + ```tsx [./utils/StaticSigner.ts] + import type { commons } from '@0xsequence/core' + import type { signers } from '@0xsequence/signhub' + import { type BytesLike, ethers } from 'ethers' + + type TransactionBundle = commons.transaction.TransactionBundle + type SignedTransactionBundle = commons.transaction.SignedTransactionBundle + type IntendedTransactionBundle = commons.transaction.IntendedTransactionBundle + + export class StaticSigner implements signers.SapientSigner { + private readonly signatureBytes: Uint8Array + private readonly savedSuffix: Uint8Array + + constructor( + private readonly address: string, + private readonly signature: string + ) { + const raw = ethers.getBytes(this.signature) + + // Separate last byte as suffix + this.savedSuffix = raw.slice(-1) + this.signatureBytes = raw.slice(0, -1) + } + + async buildDeployTransaction(): Promise { + return undefined + } + + async predecorateSignedTransactions(): Promise { + return [] + } + + async decorateTransactions( + og: IntendedTransactionBundle + ): Promise { + return og + } + + async sign(): Promise { + return this.signatureBytes + } + + notifyStatusChange(): void {} + + suffix(): BytesLike { + return this.savedSuffix + } + + async getAddress() { + return this.address + } + } + ``` + + + + Necesitamos algunos métodos utilitarios. + + Agregue este archivo en `./utils/index.ts`. + + ```tsx [index.ts] + import { Account } from '@0xsequence/account' + import { trackers } from '@0xsequence/sessions' + import { commons } from '@0xsequence/core' + import { Orchestrator, signers } from '@0xsequence/signhub' + import { allNetworks } from '@0xsequence/network' + import type { FlatTransaction, TransactionsEntry } from '../constants/types' + import { ethers } from 'ethers' + import { StaticSigner } from './StaticSigner' + + export const TRACKER = new trackers.remote.RemoteConfigTracker( + 'https://sessions.sequence.app' + ) + + export const NETWORKS = allNetworks + + /** + * Creates a new Sequence Account with the specified threshold and signers. + * + * @param threshold - The minimum weight required to authorize transactions. + * @param signers - An array of signer objects with address and weight. + * @returns A Promise that resolves to the created Account instance. + */ + export async function createSequenceAccount( + threshold: number, + signers: { address: string; weight: number }[] + ): Promise { + const account = await Account.new({ + config: { + threshold, + // By default a random checkpoint is generated every second + checkpoint: 0, + signers: signers + }, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator([]), + networks: NETWORKS + }) + + return account + } + + /** + * Converts an array of FlatTransaction objects to Sequence Transaction objects. + * + * @param txs - Array of FlatTransaction objects to convert. + * @returns An array of Sequence Transaction objects. + */ + export function toSequenceTransactions( + txs: FlatTransaction[] + ): commons.transaction.Transaction[] { + return txs.map(toSequenceTransaction) + } + + /** + * Converts a FlatTransaction object to a Sequence Transaction object. + * + * @param tx - The FlatTransaction object to convert. + * @returns The corresponding Sequence Transaction object. + */ + export function toSequenceTransaction( + tx: FlatTransaction + ): commons.transaction.Transaction { + return { + to: tx.to, + value: tx.value ? BigInt(tx.value) : undefined, + data: tx.data, + gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : undefined, + delegateCall: tx.delegateCall || false, + revertOnError: tx.revertOnError || false + } + } + + /** + * Creates an Account instance for a given address and optional signatures. + * + * @param args - Object containing the address and optional signatures array. + * @returns An Account instance configured with the provided signers. + */ + export function accountFor(args: { + address: string + signatures?: { signer: string; signature: string }[] + }) { + const signers: signers.SapientSigner[] = [] + + if (args.signatures) { + for (const { signer, signature } of args.signatures) { + const signatureArr = ethers.getBytes(signature) + if ( + signatureArr.length === 66 && + (signatureArr[64] === 0 || signatureArr[64] === 1) + ) { + signatureArr[64] = signatureArr[64] + 27 + } + + signers.push(new StaticSigner(signer, ethers.hexlify(signatureArr))) + } + } + + return new Account({ + address: args.address, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator(signers), + networks: NETWORKS + }) + } + + /** + * Computes the digest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The digest string for the transactions. + */ + export function digestOf(tx: TransactionsEntry): string { + return commons.transaction.digestOfTransactions( + commons.transaction.encodeNonce(tx.space, tx.nonce), + toSequenceTransactions(tx.transactions) + ) + } + + /** + * Computes the subdigest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The subdigest string for the transactions. + */ + export function subdigestOf(tx: TransactionsEntry): string { + const digest = digestOf(tx) + + return commons.signature.subdigestOf({ + digest, + chainId: tx.chainId, + address: tx.wallet + }) + } + + /** + * Converts Sequence Transactionish objects to an array of FlatTransaction objects. + * + * @param wallet - The wallet address associated with the transactions. + * @param txs - The Sequence Transactionish object(s) to convert. + * @returns An array of FlatTransaction objects. + */ + export function fromSequenceTransactions( + wallet: string, + txs: commons.transaction.Transactionish + ): FlatTransaction[] { + const sequenceTxs = commons.transaction.fromTransactionish(wallet, txs) + return sequenceTxs.map((stx) => ({ + to: stx.to, + value: stx.value?.toString(), + data: stx.data?.toString(), + gasLimit: stx.gasLimit?.toString(), + delegateCall: stx.delegateCall, + revertOnError: stx.revertOnError + })) + } + + /** + * Recovers the signer addresses from an array of signatures and a subdigest. + * + * @param signatures - Array of signature strings to recover signers from. + * @param subdigest - The subdigest string used for recovery. + * @returns An array of objects containing the signer address and signature. + */ + export function recoverSigner( + signatures: string[], + subdigest: string + ): { signer: string; signature: string }[] { + const res: { signer: string; signature: string }[] = [] + + for (const signature of signatures) { + try { + const r = commons.signer.recoverSigner(subdigest, signature) + res.push({ signer: r, signature: signature }) + } catch (e) { + console.error('Failed to recover signature', e) + } + } + + return res + } + ``` + + + + ```tsx [page.tsx] + "use client" + + import { usePublicClient, useSignMessage } from "wagmi" + import { accountFor, createSequenceAccount, subdigestOf, toSequenceTransactions } from "./utils" + import { useState, useEffect } from "react" + import { commons } from "@0xsequence/core" + import { ethers } from "ethers" + import { zeroAddress } from "viem" + import { usePrivy } from "@privy-io/react-auth" + + const CHAIN_ID = 84532 + + export default function Home() { + const { ready, authenticated, login, logout, user } = usePrivy() + const publicClient = usePublicClient({ chainId: CHAIN_ID }) + const { signMessageAsync } = useSignMessage() + const [walletAddress, setWalletAddress] = useState<`0x${string}` | null>(null) + const [txHash, setTxHash] = useState(null) + const [loadingSendTx, setLoadingSendTx] = useState(false) + const [isWalletDeployed, setIsWalletDeployed] = useState(false) + const [checkingWalletDeployed, setCheckingWalletDeployed] = useState(true) + + useEffect(() => { + const createWallet = async () => { + if (user?.wallet && user.wallet.address) { + const seqeunceAccount = await createSequenceAccount(1, [ + { address: user.wallet.address, weight: 1 }, + ]) + const accountWithSig = accountFor({ + address: seqeunceAccount.address, + }) + const status = await accountWithSig.status(CHAIN_ID) + const wallet = accountWithSig.walletForStatus(CHAIN_ID, status) + setCheckingWalletDeployed(true) + const hasCode = await publicClient?.getCode({ address: accountWithSig.address as `0x${string}` }) + setCheckingWalletDeployed(false) + if (!hasCode) { + wallet.deploy() + // Wait for the wallet to be deploy, most of the times it takes less than 4 seconds + await new Promise((resolve) => setTimeout(resolve, 4000)) + } + setWalletAddress(wallet.address as `0x${string}`) + setIsWalletDeployed(true) + setCheckingWalletDeployed(false) + } else { + setWalletAddress(null) + setTxHash(null) + } + } + createWallet() + }, [user]) + + const handleSend = async () => { + if (!user?.wallet?.address || !walletAddress) return + setLoadingSendTx(true) + const txs = [ + { to: zeroAddress, data: "0x", value: "0", revertOnError: true }, + ] + const txe = { + wallet: walletAddress, + space: Date.now().toString(), + nonce: "0", + chainId: CHAIN_ID.toString(), + transactions: txs, + } + const subdigest = subdigestOf(txe) + const digestBytes = ethers.getBytes(subdigest) + const signature = await signMessageAsync({ message: { raw: digestBytes } }) + const suffixed = signature + "02" + const account = accountFor({ + address: walletAddress, + signatures: [ + { signer: user.wallet.address as `0x${string}`, signature: suffixed }, + ], + }) + const sequenceTxs = toSequenceTransactions(txs) + const status = await account.status(CHAIN_ID) + const wallet = account.walletForStatus(CHAIN_ID, status) + const signed = await wallet.signTransactions( + sequenceTxs, + commons.transaction.encodeNonce(txe.space, txe.nonce) + ) + const relayer = account.relayer(CHAIN_ID) + const relayed = await relayer.relay(signed) + setTxHash(relayed?.hash || null) + setLoadingSendTx(false) + } + + if (!ready) + return ( +
+ Loading Privy... +
+ ) + + return ( +
+ + {isWalletDeployed ? ( +
+
Smart Wallet Address
+
{walletAddress}
+
+ ) : ( +
+ {checkingWalletDeployed ? ( +
Checking if wallet is deployed...
+ ) : ( +
Deploying Sequence Smart Wallet...
+ )} +
+ )} + {walletAddress && ( + + )} + {txHash && ( +
+
Transaction Hash
+
{txHash}
+
+ )} +
+ ) + } + ``` +
+ + + ```bash + pnpm dev + ``` + +
\ No newline at end of file diff --git a/ja/sdk/headless-wallet/use-with-privy.mdx b/ja/sdk/headless-wallet/use-with-privy.mdx new file mode 100644 index 00000000..5247e77f --- /dev/null +++ b/ja/sdk/headless-wallet/use-with-privy.mdx @@ -0,0 +1,558 @@ +--- +title: Privy と Sequence +description: Privy を Sequence スマートウォレットの署名者として利用する方法をご紹介します。 +--- + +このガイドでは、主要な `sequence.js` ライブラリを使って Privy と Sequence をシームレスに統合し、ユーザーが Sequence スマートウォレットを通じて dApp にサインインし操作できるようにする方法を解説します。具体的には、ユーザーの Privy で管理された EOA によって制御される Sequence ウォレットを作成し、そのウォレットを使って Base Sepolia 上でガスレス取引を行います。 + + + Next.js 15、React 19、Tailwind CSS 4 を使用します。 + + + + + Sequence、Privy、wagmi/viem の各パッケージが必要です。 + + ```bash + pnpm install @0xsequence/account @0xsequence/core @0xsequence/network @0xsequence/sessions @0xsequence/signhub @privy-io/react-auth @privy-io/wagmi-connector wagmi @privy-io/wagmi @tanstack/react-query viem ethers + ``` + + + + Privy App ID と Client ID を取得する必要があります。[Privy Dashboard](https://dashboard.privy.io/apps) で新しいアプリを作成すると取得できます。 + プロジェクトのルートに `.env.local` ファイルを作成し、`NEXT_PUBLIC_PRIVY_APP_ID` と `NEXT_PUBLIC_PRIVY_CLIENT_ID` を追加してください。 + + + + `providers.tsx` ファイルで `WagmiProvider` と `PrivyProvider` をセットアップします。 + これにより、アプリで Privy を認証に、wagmi をウォレット連携に利用できるようになります。 + + ```tsx [providers.tsx] + 'use client' + + import { type PrivyClientConfig, PrivyProvider } from '@privy-io/react-auth' + import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + import { createConfig, WagmiProvider } from '@privy-io/wagmi' + import { baseSepolia } from 'viem/chains' + import { http } from 'wagmi' + + const queryClient = new QueryClient() + + const wagmiConfig = createConfig({ + chains: [baseSepolia], + transports: { + [baseSepolia.id]: http() + } + }) + + const privyConfig: PrivyClientConfig = { + embeddedWallets: { + requireUserPasswordOnCreate: true, + showWalletUIs: true + }, + loginMethods: ['wallet', 'email', 'google'], + appearance: { + showWalletLoginFirst: true + }, + defaultChain: baseSepolia + } + + const APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID + const CLIENT_ID = process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID + + export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ) + } + ``` + + + + アプリのレイアウト全体をプロバイダーでラップします。 + + ```tsx [layout.tsx] + import type { Metadata } from 'next' + import { Geist, Geist_Mono } from 'next/font/google' + import './globals.css' + import Providers from './providers' + + const geistSans = Geist({ + variable: '--font-geist-sans', + subsets: ['latin'] + }) + + const geistMono = Geist_Mono({ + variable: '--font-geist-mono', + subsets: ['latin'] + }) + + export const metadata: Metadata = { + title: 'Privy + Sequence', + description: 'A demo showcasing how Sequence can be used with Privy' + } + + export default function RootLayout({ + children + }: Readonly<{ + children: React.ReactNode + }>) { + return ( + + + {children} + + + ) + } + ``` + + + + `./constants/types.ts` に以下の型を追加してください。 + + ```tsx [./constants/types.ts] + export type FlatTransaction = { + to: string + value?: string + data?: string + gasLimit?: string + delegateCall?: boolean + revertOnError?: boolean + } + + export type TransactionsEntry = { + subdigest?: string + wallet: string + space: string + nonce: string + chainId: string + transactions: FlatTransaction[] + } + ``` + + + + `./utils/StaticSigner.ts` に `StaticSigner` クラスを作成します。 + + ```tsx [./utils/StaticSigner.ts] + import type { commons } from '@0xsequence/core' + import type { signers } from '@0xsequence/signhub' + import { type BytesLike, ethers } from 'ethers' + + type TransactionBundle = commons.transaction.TransactionBundle + type SignedTransactionBundle = commons.transaction.SignedTransactionBundle + type IntendedTransactionBundle = commons.transaction.IntendedTransactionBundle + + export class StaticSigner implements signers.SapientSigner { + private readonly signatureBytes: Uint8Array + private readonly savedSuffix: Uint8Array + + constructor( + private readonly address: string, + private readonly signature: string + ) { + const raw = ethers.getBytes(this.signature) + + // Separate last byte as suffix + this.savedSuffix = raw.slice(-1) + this.signatureBytes = raw.slice(0, -1) + } + + async buildDeployTransaction(): Promise { + return undefined + } + + async predecorateSignedTransactions(): Promise { + return [] + } + + async decorateTransactions( + og: IntendedTransactionBundle + ): Promise { + return og + } + + async sign(): Promise { + return this.signatureBytes + } + + notifyStatusChange(): void {} + + suffix(): BytesLike { + return this.savedSuffix + } + + async getAddress() { + return this.address + } + } + ``` + + + + いくつかのユーティリティメソッドが必要です。 + + このファイルを `./utils/index.ts` に追加してください。 + + ```tsx [index.ts] + import { Account } from '@0xsequence/account' + import { trackers } from '@0xsequence/sessions' + import { commons } from '@0xsequence/core' + import { Orchestrator, signers } from '@0xsequence/signhub' + import { allNetworks } from '@0xsequence/network' + import type { FlatTransaction, TransactionsEntry } from '../constants/types' + import { ethers } from 'ethers' + import { StaticSigner } from './StaticSigner' + + export const TRACKER = new trackers.remote.RemoteConfigTracker( + 'https://sessions.sequence.app' + ) + + export const NETWORKS = allNetworks + + /** + * Creates a new Sequence Account with the specified threshold and signers. + * + * @param threshold - The minimum weight required to authorize transactions. + * @param signers - An array of signer objects with address and weight. + * @returns A Promise that resolves to the created Account instance. + */ + export async function createSequenceAccount( + threshold: number, + signers: { address: string; weight: number }[] + ): Promise { + const account = await Account.new({ + config: { + threshold, + // By default a random checkpoint is generated every second + checkpoint: 0, + signers: signers + }, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator([]), + networks: NETWORKS + }) + + return account + } + + /** + * Converts an array of FlatTransaction objects to Sequence Transaction objects. + * + * @param txs - Array of FlatTransaction objects to convert. + * @returns An array of Sequence Transaction objects. + */ + export function toSequenceTransactions( + txs: FlatTransaction[] + ): commons.transaction.Transaction[] { + return txs.map(toSequenceTransaction) + } + + /** + * Converts a FlatTransaction object to a Sequence Transaction object. + * + * @param tx - The FlatTransaction object to convert. + * @returns The corresponding Sequence Transaction object. + */ + export function toSequenceTransaction( + tx: FlatTransaction + ): commons.transaction.Transaction { + return { + to: tx.to, + value: tx.value ? BigInt(tx.value) : undefined, + data: tx.data, + gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : undefined, + delegateCall: tx.delegateCall || false, + revertOnError: tx.revertOnError || false + } + } + + /** + * Creates an Account instance for a given address and optional signatures. + * + * @param args - Object containing the address and optional signatures array. + * @returns An Account instance configured with the provided signers. + */ + export function accountFor(args: { + address: string + signatures?: { signer: string; signature: string }[] + }) { + const signers: signers.SapientSigner[] = [] + + if (args.signatures) { + for (const { signer, signature } of args.signatures) { + const signatureArr = ethers.getBytes(signature) + if ( + signatureArr.length === 66 && + (signatureArr[64] === 0 || signatureArr[64] === 1) + ) { + signatureArr[64] = signatureArr[64] + 27 + } + + signers.push(new StaticSigner(signer, ethers.hexlify(signatureArr))) + } + } + + return new Account({ + address: args.address, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator(signers), + networks: NETWORKS + }) + } + + /** + * Computes the digest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The digest string for the transactions. + */ + export function digestOf(tx: TransactionsEntry): string { + return commons.transaction.digestOfTransactions( + commons.transaction.encodeNonce(tx.space, tx.nonce), + toSequenceTransactions(tx.transactions) + ) + } + + /** + * Computes the subdigest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The subdigest string for the transactions. + */ + export function subdigestOf(tx: TransactionsEntry): string { + const digest = digestOf(tx) + + return commons.signature.subdigestOf({ + digest, + chainId: tx.chainId, + address: tx.wallet + }) + } + + /** + * Converts Sequence Transactionish objects to an array of FlatTransaction objects. + * + * @param wallet - The wallet address associated with the transactions. + * @param txs - The Sequence Transactionish object(s) to convert. + * @returns An array of FlatTransaction objects. + */ + export function fromSequenceTransactions( + wallet: string, + txs: commons.transaction.Transactionish + ): FlatTransaction[] { + const sequenceTxs = commons.transaction.fromTransactionish(wallet, txs) + return sequenceTxs.map((stx) => ({ + to: stx.to, + value: stx.value?.toString(), + data: stx.data?.toString(), + gasLimit: stx.gasLimit?.toString(), + delegateCall: stx.delegateCall, + revertOnError: stx.revertOnError + })) + } + + /** + * Recovers the signer addresses from an array of signatures and a subdigest. + * + * @param signatures - Array of signature strings to recover signers from. + * @param subdigest - The subdigest string used for recovery. + * @returns An array of objects containing the signer address and signature. + */ + export function recoverSigner( + signatures: string[], + subdigest: string + ): { signer: string; signature: string }[] { + const res: { signer: string; signature: string }[] = [] + + for (const signature of signatures) { + try { + const r = commons.signer.recoverSigner(subdigest, signature) + res.push({ signer: r, signature: signature }) + } catch (e) { + console.error('Failed to recover signature', e) + } + } + + return res + } + ``` + + + + ```tsx [page.tsx] + "use client" + + import { usePublicClient, useSignMessage } from "wagmi" + import { accountFor, createSequenceAccount, subdigestOf, toSequenceTransactions } from "./utils" + import { useState, useEffect } from "react" + import { commons } from "@0xsequence/core" + import { ethers } from "ethers" + import { zeroAddress } from "viem" + import { usePrivy } from "@privy-io/react-auth" + + const CHAIN_ID = 84532 + + export default function Home() { + const { ready, authenticated, login, logout, user } = usePrivy() + const publicClient = usePublicClient({ chainId: CHAIN_ID }) + const { signMessageAsync } = useSignMessage() + const [walletAddress, setWalletAddress] = useState<`0x${string}` | null>(null) + const [txHash, setTxHash] = useState(null) + const [loadingSendTx, setLoadingSendTx] = useState(false) + const [isWalletDeployed, setIsWalletDeployed] = useState(false) + const [checkingWalletDeployed, setCheckingWalletDeployed] = useState(true) + + useEffect(() => { + const createWallet = async () => { + if (user?.wallet && user.wallet.address) { + const seqeunceAccount = await createSequenceAccount(1, [ + { address: user.wallet.address, weight: 1 }, + ]) + const accountWithSig = accountFor({ + address: seqeunceAccount.address, + }) + const status = await accountWithSig.status(CHAIN_ID) + const wallet = accountWithSig.walletForStatus(CHAIN_ID, status) + setCheckingWalletDeployed(true) + const hasCode = await publicClient?.getCode({ address: accountWithSig.address as `0x${string}` }) + setCheckingWalletDeployed(false) + if (!hasCode) { + wallet.deploy() + // Wait for the wallet to be deploy, most of the times it takes less than 4 seconds + await new Promise((resolve) => setTimeout(resolve, 4000)) + } + setWalletAddress(wallet.address as `0x${string}`) + setIsWalletDeployed(true) + setCheckingWalletDeployed(false) + } else { + setWalletAddress(null) + setTxHash(null) + } + } + createWallet() + }, [user]) + + const handleSend = async () => { + if (!user?.wallet?.address || !walletAddress) return + setLoadingSendTx(true) + const txs = [ + { to: zeroAddress, data: "0x", value: "0", revertOnError: true }, + ] + const txe = { + wallet: walletAddress, + space: Date.now().toString(), + nonce: "0", + chainId: CHAIN_ID.toString(), + transactions: txs, + } + const subdigest = subdigestOf(txe) + const digestBytes = ethers.getBytes(subdigest) + const signature = await signMessageAsync({ message: { raw: digestBytes } }) + const suffixed = signature + "02" + const account = accountFor({ + address: walletAddress, + signatures: [ + { signer: user.wallet.address as `0x${string}`, signature: suffixed }, + ], + }) + const sequenceTxs = toSequenceTransactions(txs) + const status = await account.status(CHAIN_ID) + const wallet = account.walletForStatus(CHAIN_ID, status) + const signed = await wallet.signTransactions( + sequenceTxs, + commons.transaction.encodeNonce(txe.space, txe.nonce) + ) + const relayer = account.relayer(CHAIN_ID) + const relayed = await relayer.relay(signed) + setTxHash(relayed?.hash || null) + setLoadingSendTx(false) + } + + if (!ready) + return ( +
+ Loading Privy... +
+ ) + + return ( +
+ + {isWalletDeployed ? ( +
+
Smart Wallet Address
+
{walletAddress}
+
+ ) : ( +
+ {checkingWalletDeployed ? ( +
Checking if wallet is deployed...
+ ) : ( +
Deploying Sequence Smart Wallet...
+ )} +
+ )} + {walletAddress && ( + + )} + {txHash && ( +
+
Transaction Hash
+
{txHash}
+
+ )} +
+ ) + } + ``` +
+ + + ```bash + pnpm dev + ``` + +
\ No newline at end of file From f58a1fcb0095ba03d37f68eedf6140dadd44d9f4 Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Thu, 26 Jun 2025 20:51:10 +0300 Subject: [PATCH 3/7] added privy & sequence guide card --- docs.json | 3 +- guides/guide-overview.mdx | 8 + guides/use-with-privy.mdx | 561 ++++++++++++++++++++++++++ images/guides/overview/privy_logo.png | Bin 0 -> 21561 bytes 4 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 guides/use-with-privy.mdx create mode 100644 images/guides/overview/privy_logo.png diff --git a/docs.json b/docs.json index 5cfa2cb2..795acda9 100644 --- a/docs.json +++ b/docs.json @@ -839,7 +839,8 @@ "guides/typed-on-chain-signatures", "guides/building-relaying-server", "guides/analytics-guide", - "guides/build-embedding-wallet" + "guides/build-embedding-wallet", + "guides/use-with-privy" ] }, { diff --git a/guides/guide-overview.mdx b/guides/guide-overview.mdx index 4a524199..c5662c88 100644 --- a/guides/guide-overview.mdx +++ b/guides/guide-overview.mdx @@ -75,6 +75,14 @@ Follow our step-by-step guides and open source code templates to accelerate your Leveraging Sequence's Transaction API and a serverless environment, you will build a scalable minting service for NFT mints or any other transactions that automatically handles blockchain complexities like reorgs, nonce management, and transaction parallelization. + + Learn how to connect Privy with Sequence. + + +We will be using Next.js 15, React 19, and Tailwind CSS 4. + + + + + +You'll need the Sequence, Privy, and wagmi/viem packages. + +```bash +pnpm install @0xsequence/account @0xsequence/core @0xsequence/network @0xsequence/sessions @0xsequence/signhub @privy-io/react-auth @privy-io/wagmi-connector wagmi @privy-io/wagmi @tanstack/react-query viem ethers +``` + + + +You'll need to get a Privy App ID and Client ID. You can get these by creating a new app in the [Privy Dashboard](https://dashboard.privy.io/apps). +Create an `.env.local` file in your project root and add your `NEXT_PUBLIC_PRIVY_APP_ID` and `NEXT_PUBLIC_PRIVY_CLIENT_ID`. + + + +Set up `WagmiProvider` and `PrivyProvider` in your a `providers.tsx` file. +We do this to allow our app to use both Privy for authentication and wagmi for wallet interactions. + +```tsx [providers.tsx] +'use client' + +import { type PrivyClientConfig, PrivyProvider } from '@privy-io/react-auth' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { createConfig, WagmiProvider } from '@privy-io/wagmi' +import { baseSepolia } from 'viem/chains' +import { http } from 'wagmi' + +const queryClient = new QueryClient() + +const wagmiConfig = createConfig({ + chains: [baseSepolia], + transports: { + [baseSepolia.id]: http() + } +}) + +const privyConfig: PrivyClientConfig = { + embeddedWallets: { + requireUserPasswordOnCreate: true, + showWalletUIs: true + }, + loginMethods: ['wallet', 'email', 'google'], + appearance: { + showWalletLoginFirst: true + }, + defaultChain: baseSepolia +} + +const APP_ID = process.env.NEXT_PUBLIC_PRIVY_APP_ID +const CLIENT_ID = process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID + +export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + ) +} +``` + + + +Wrap the App's layout with the Providers. + +```tsx [layout.tsx] +import type { Metadata } from 'next' +import { Geist, Geist_Mono } from 'next/font/google' +import './globals.css' +import Providers from './providers' + +const geistSans = Geist({ + variable: '--font-geist-sans', + subsets: ['latin'] +}) + +const geistMono = Geist_Mono({ + variable: '--font-geist-mono', + subsets: ['latin'] +}) + +export const metadata: Metadata = { + title: 'Privy + Sequence', + description: 'A demo showcasing how Sequence can be used with Privy' +} + +export default function RootLayout({ + children +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + + + ) +} +``` + + + +Add the following types to `./constants/types.ts`. + +```tsx [./constants/types.ts] +export type FlatTransaction = { + to: string + value?: string + data?: string + gasLimit?: string + delegateCall?: boolean + revertOnError?: boolean +} + +export type TransactionsEntry = { + subdigest?: string + wallet: string + space: string + nonce: string + chainId: string + transactions: FlatTransaction[] +} +``` + + + +Create a `StaticSigner` class in `./utils/StaticSigner.ts`. + +```tsx [./utils/StaticSigner.ts] +import type { commons } from '@0xsequence/core' +import type { signers } from '@0xsequence/signhub' +import { type BytesLike, ethers } from 'ethers' + +type TransactionBundle = commons.transaction.TransactionBundle +type SignedTransactionBundle = commons.transaction.SignedTransactionBundle +type IntendedTransactionBundle = commons.transaction.IntendedTransactionBundle + +export class StaticSigner implements signers.SapientSigner { + private readonly signatureBytes: Uint8Array + private readonly savedSuffix: Uint8Array + + constructor( + private readonly address: string, + private readonly signature: string + ) { + const raw = ethers.getBytes(this.signature) + + // Separate last byte as suffix + this.savedSuffix = raw.slice(-1) + this.signatureBytes = raw.slice(0, -1) + } + + async buildDeployTransaction(): Promise { + return undefined + } + + async predecorateSignedTransactions(): Promise { + return [] + } + + async decorateTransactions( + og: IntendedTransactionBundle + ): Promise { + return og + } + + async sign(): Promise { + return this.signatureBytes + } + + notifyStatusChange(): void {} + + suffix(): BytesLike { + return this.savedSuffix + } + + async getAddress() { + return this.address + } +} +``` + + + +We need a couple of utility methods. + +Add this file in `./utils/index.ts`. + +```tsx [index.ts] +import { Account } from '@0xsequence/account' +import { trackers } from '@0xsequence/sessions' +import { commons } from '@0xsequence/core' +import { Orchestrator, signers } from '@0xsequence/signhub' +import { allNetworks } from '@0xsequence/network' +import type { FlatTransaction, TransactionsEntry } from '../constants/types' +import { ethers } from 'ethers' +import { StaticSigner } from './StaticSigner' + +export const TRACKER = new trackers.remote.RemoteConfigTracker( + 'https://sessions.sequence.app' +) + +export const NETWORKS = allNetworks + +/** + * Creates a new Sequence Account with the specified threshold and signers. + * + * @param threshold - The minimum weight required to authorize transactions. + * @param signers - An array of signer objects with address and weight. + * @returns A Promise that resolves to the created Account instance. + */ +export async function createSequenceAccount( + threshold: number, + signers: { address: string; weight: number }[] +): Promise { + const account = await Account.new({ + config: { + threshold, + // By default a random checkpoint is generated every second + checkpoint: 0, + signers: signers + }, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator([]), + networks: NETWORKS + }) + + return account +} + +/** + * Converts an array of FlatTransaction objects to Sequence Transaction objects. + * + * @param txs - Array of FlatTransaction objects to convert. + * @returns An array of Sequence Transaction objects. + */ +export function toSequenceTransactions( + txs: FlatTransaction[] +): commons.transaction.Transaction[] { + return txs.map(toSequenceTransaction) +} + +/** + * Converts a FlatTransaction object to a Sequence Transaction object. + * + * @param tx - The FlatTransaction object to convert. + * @returns The corresponding Sequence Transaction object. + */ +export function toSequenceTransaction( + tx: FlatTransaction +): commons.transaction.Transaction { + return { + to: tx.to, + value: tx.value ? BigInt(tx.value) : undefined, + data: tx.data, + gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : undefined, + delegateCall: tx.delegateCall || false, + revertOnError: tx.revertOnError || false + } +} + +/** + * Creates an Account instance for a given address and optional signatures. + * + * @param args - Object containing the address and optional signatures array. + * @returns An Account instance configured with the provided signers. + */ +export function accountFor(args: { + address: string + signatures?: { signer: string; signature: string }[] +}) { + const signers: signers.SapientSigner[] = [] + + if (args.signatures) { + for (const { signer, signature } of args.signatures) { + const signatureArr = ethers.getBytes(signature) + if ( + signatureArr.length === 66 && + (signatureArr[64] === 0 || signatureArr[64] === 1) + ) { + signatureArr[64] = signatureArr[64] + 27 + } + + signers.push(new StaticSigner(signer, ethers.hexlify(signatureArr))) + } + } + + return new Account({ + address: args.address, + tracker: TRACKER, + contexts: commons.context.defaultContexts, + orchestrator: new Orchestrator(signers), + networks: NETWORKS + }) +} + +/** + * Computes the digest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The digest string for the transactions. + */ +export function digestOf(tx: TransactionsEntry): string { + return commons.transaction.digestOfTransactions( + commons.transaction.encodeNonce(tx.space, tx.nonce), + toSequenceTransactions(tx.transactions) + ) +} + +/** + * Computes the subdigest for a given TransactionsEntry. + * + * @param tx - The TransactionsEntry containing transaction details. + * @returns The subdigest string for the transactions. + */ +export function subdigestOf(tx: TransactionsEntry): string { + const digest = digestOf(tx) + + return commons.signature.subdigestOf({ + digest, + chainId: tx.chainId, + address: tx.wallet + }) +} + +/** + * Converts Sequence Transactionish objects to an array of FlatTransaction objects. + * + * @param wallet - The wallet address associated with the transactions. + * @param txs - The Sequence Transactionish object(s) to convert. + * @returns An array of FlatTransaction objects. + */ +export function fromSequenceTransactions( + wallet: string, + txs: commons.transaction.Transactionish +): FlatTransaction[] { + const sequenceTxs = commons.transaction.fromTransactionish(wallet, txs) + return sequenceTxs.map((stx) => ({ + to: stx.to, + value: stx.value?.toString(), + data: stx.data?.toString(), + gasLimit: stx.gasLimit?.toString(), + delegateCall: stx.delegateCall, + revertOnError: stx.revertOnError + })) +} + +/** + * Recovers the signer addresses from an array of signatures and a subdigest. + * + * @param signatures - Array of signature strings to recover signers from. + * @param subdigest - The subdigest string used for recovery. + * @returns An array of objects containing the signer address and signature. + */ +export function recoverSigner( + signatures: string[], + subdigest: string +): { signer: string; signature: string }[] { + const res: { signer: string; signature: string }[] = [] + + for (const signature of signatures) { + try { + const r = commons.signer.recoverSigner(subdigest, signature) + res.push({ signer: r, signature: signature }) + } catch (e) { + console.error('Failed to recover signature', e) + } + } + + return res +} +``` + + + + +```tsx [page.tsx] +"use client" + +import { usePublicClient, useSignMessage } from "wagmi" +import { accountFor, createSequenceAccount, subdigestOf, toSequenceTransactions } from "./utils" +import { useState, useEffect } from "react" +import { commons } from "@0xsequence/core" +import { ethers } from "ethers" +import { zeroAddress } from "viem" +import { usePrivy } from "@privy-io/react-auth" + +const CHAIN_ID = 84532 + +export default function Home() { + const { ready, authenticated, login, logout, user } = usePrivy() + const publicClient = usePublicClient({ chainId: CHAIN_ID }) + const { signMessageAsync } = useSignMessage() + const [walletAddress, setWalletAddress] = useState<`0x${string}` | null>(null) + const [txHash, setTxHash] = useState(null) + const [loadingSendTx, setLoadingSendTx] = useState(false) + const [isWalletDeployed, setIsWalletDeployed] = useState(false) + const [checkingWalletDeployed, setCheckingWalletDeployed] = useState(true) + + useEffect(() => { + const createWallet = async () => { + if (user?.wallet && user.wallet.address) { + const seqeunceAccount = await createSequenceAccount(1, [ + { address: user.wallet.address, weight: 1 }, + ]) + const accountWithSig = accountFor({ + address: seqeunceAccount.address, + }) + const status = await accountWithSig.status(CHAIN_ID) + const wallet = accountWithSig.walletForStatus(CHAIN_ID, status) + setCheckingWalletDeployed(true) + const hasCode = await publicClient?.getCode({ address: accountWithSig.address as `0x${string}` }) + setCheckingWalletDeployed(false) + if (!hasCode) { + wallet.deploy() + // Wait for the wallet to be deploy, most of the times it takes less than 4 seconds + await new Promise((resolve) => setTimeout(resolve, 4000)) + } + setWalletAddress(wallet.address as `0x${string}`) + setIsWalletDeployed(true) + setCheckingWalletDeployed(false) + } else { + setWalletAddress(null) + setTxHash(null) + } + } + createWallet() + }, [user]) + + const handleSend = async () => { + if (!user?.wallet?.address || !walletAddress) return + setLoadingSendTx(true) + const txs = [ + { to: zeroAddress, data: "0x", value: "0", revertOnError: true }, + ] + const txe = { + wallet: walletAddress, + space: Date.now().toString(), + nonce: "0", + chainId: CHAIN_ID.toString(), + transactions: txs, + } + const subdigest = subdigestOf(txe) + const digestBytes = ethers.getBytes(subdigest) + const signature = await signMessageAsync({ message: { raw: digestBytes } }) + const suffixed = signature + "02" + const account = accountFor({ + address: walletAddress, + signatures: [ + { signer: user.wallet.address as `0x${string}`, signature: suffixed }, + ], + }) + const sequenceTxs = toSequenceTransactions(txs) + const status = await account.status(CHAIN_ID) + const wallet = account.walletForStatus(CHAIN_ID, status) + const signed = await wallet.signTransactions( + sequenceTxs, + commons.transaction.encodeNonce(txe.space, txe.nonce) + ) + const relayer = account.relayer(CHAIN_ID) + const relayed = await relayer.relay(signed) + setTxHash(relayed?.hash || null) + setLoadingSendTx(false) + } + + if (!ready) + return ( +
+ Loading Privy... +
+ ) + + return ( +
+ + {isWalletDeployed ? ( +
+
Smart Wallet Address
+
{walletAddress}
+
+ ) : ( +
+ {checkingWalletDeployed ? ( +
Checking if wallet is deployed...
+ ) : ( +
Deploying Sequence Smart Wallet...
+ )} +
+ )} + {walletAddress && ( + + )} + {txHash && ( +
+
Transaction Hash
+
{txHash}
+
+ )} +
+ ) +} +``` +
+ + + +```bash +pnpm dev +``` + +
\ No newline at end of file diff --git a/images/guides/overview/privy_logo.png b/images/guides/overview/privy_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b53dcc64c56fd7f9af6a44c351ac19de8ed8cbe5 GIT binary patch literal 21561 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 literal 0 HcmV?d00001 From 4ea29a42234b78560b400ebb4c8322ff8b47f63a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 26 Jun 2025 17:51:45 +0000 Subject: [PATCH 4/7] Auto-update guide cards metadata [skip ci] --- guides/guide-cards.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/guides/guide-cards.json b/guides/guide-cards.json index fec939d1..1d88957c 100644 --- a/guides/guide-cards.json +++ b/guides/guide-cards.json @@ -1,6 +1,6 @@ { - "lastUpdated": "2025-06-04T08:39:42.458Z", - "totalCards": 11, + "lastUpdated": "2025-06-26T17:51:44.532Z", + "totalCards": 12, "sections": [ { "title": "Game Developers", @@ -58,6 +58,12 @@ "href": "/guides/mint-collectibles-serverless", "description": "Leveraging Sequence's Transaction API and a serverless environment, you will build a scalable minting service for NFT mints or any other transactions that automatically handles blockchain complexities like reorgs, nonce management, and transaction parallelization." }, + { + "title": "Use Privy with Sequence", + "img": "/images/guides/overview/privy_logo.png", + "href": "/guides/use-with-privy", + "description": "Learn how to connect Privy with Sequence." + }, { "title": "Creating a Custom Marketplace with Sequence", "img": "/images/guides/overview/marketplace.png", From 68f996fcc613ee28ca55954250ecb6532160a84c Mon Sep 17 00:00:00 2001 From: VGabriel45 Date: Wed, 9 Jul 2025 13:28:02 +0300 Subject: [PATCH 5/7] change image --- guides/guide-overview.mdx | 2 +- images/guides/overview/privyLogo.png | Bin 0 -> 12833 bytes images/guides/overview/privy_logo.png | Bin 21561 -> 0 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 images/guides/overview/privyLogo.png delete mode 100644 images/guides/overview/privy_logo.png diff --git a/guides/guide-overview.mdx b/guides/guide-overview.mdx index c5662c88..ccca5ada 100644 --- a/guides/guide-overview.mdx +++ b/guides/guide-overview.mdx @@ -77,7 +77,7 @@ Follow our step-by-step guides and open source code templates to accelerate your Learn how to connect Privy with Sequence. diff --git a/images/guides/overview/privyLogo.png b/images/guides/overview/privyLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..ab22aef8a2efdc716749a6637f3d44fba5778db8 GIT binary patch literal 12833 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‚TzjVqqhErgL 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 literal 0 HcmV?d00001 diff --git a/images/guides/overview/privy_logo.png b/images/guides/overview/privy_logo.png deleted file mode 100644 index b53dcc64c56fd7f9af6a44c351ac19de8ed8cbe5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21561 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 From 0de43ab4f872f6f4712f6e6b9595e0e23d270ea0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Jul 2025 10:28:35 +0000 Subject: [PATCH 6/7] Auto-update guide cards metadata [skip ci] --- guides/guide-cards.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/guide-cards.json b/guides/guide-cards.json index 1d88957c..27394e5c 100644 --- a/guides/guide-cards.json +++ b/guides/guide-cards.json @@ -1,5 +1,5 @@ { - "lastUpdated": "2025-06-26T17:51:44.532Z", + "lastUpdated": "2025-07-09T10:28:35.469Z", "totalCards": 12, "sections": [ { @@ -60,7 +60,7 @@ }, { "title": "Use Privy with Sequence", - "img": "/images/guides/overview/privy_logo.png", + "img": "/images/guides/overview/privyLogo.png", "href": "/guides/use-with-privy", "description": "Learn how to connect Privy with Sequence." }, From 1637e77e9a7b7a496268e37dc331e56594bfec7e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 9 Jul 2025 10:39:30 +0000 Subject: [PATCH 7/7] chore(i18n): update translations [en] Sync file structure, format locales. Branch: 138/merge --- es/guides/guide-cards.json | 2 +- es/guides/guide-overview.mdx | 6 +++- es/sdk/unity/power/wallet-ui.mdx | 50 -------------------------------- ja/guides/guide-cards.json | 2 +- ja/guides/guide-overview.mdx | 4 +++ ja/sdk/unity/power/wallet-ui.mdx | 50 -------------------------------- 6 files changed, 11 insertions(+), 103 deletions(-) delete mode 100644 es/sdk/unity/power/wallet-ui.mdx delete mode 100644 ja/sdk/unity/power/wallet-ui.mdx diff --git a/es/guides/guide-cards.json b/es/guides/guide-cards.json index 194a3a83..037e16b4 100644 --- a/es/guides/guide-cards.json +++ b/es/guides/guide-cards.json @@ -1 +1 @@ -{"lastUpdated":"2025-06-04T08:39:42.458Z","totalCards":11,"sections":[{"title":"Desarrolladores de videojuegos","cards":[{"title":"Cree un juego móvil con Unity + Sequence","img":"/images/unity/jellyforest.webp","href":"/guias/jelly-forest-unity-guia","description":"Aprenda a crear un juego atractivo para iOS y Android que utiliza Sequence Embedded Wallets en segundo plano para habilitar un marketplace integrado y una moneda dentro del juego."},{"title":"Integre Sequence Wallets con una Mini-App de Telegram","img":"/images/guides/telegram/telegram-miniapp.webp","href":"/guias/integracion-telegram","description":"Siga nuestra guía de integración para aprender cómo integrar una Sequence Embedded Wallet en una aplicación de Telegram y así brindar soporte a sus usuarios en cadenas EVM."},{"title":"Cómo crear un juego con Unreal Engine","img":"/images/guides/overview/unreal-ew-guide.png","href":"/guias/unreal-ew-guia","description":"Utilice el SDK de Unreal de Sequence para mostrar información de Embedded Wallet, firmar mensajes y enviar transacciones."},{"title":"Cree un juego Dungeon Crawler con recompensas generadas por IA","img":"/images/guides/treasure-chest/dungeonMinter.png","href":"/guias/tesoro-guia","description":"Con este tutorial, cree un laberinto web donde los objetos de caja de recompensas (lootbox) se generan usando IA y se mintean dinámicamente en el wallet universal del jugador."},{"title":"Cree un juego WebGL en Typescript","img":"/images/aviator.png","href":"/guias/webgl-guia","description":"Siga una guía paso a paso para crear una demo de juego web que utiliza Sequence Embedded Wallet y tokens de logros personalizados dentro del juego."},{"title":"Venda objetos del juego a través de una tienda web","img":"/images/guides/overview/primary-sales.png","href":"/guias/ventas-primarias","description":"Impulse el crecimiento de su juego vendiendo objetos directamente a sus jugadores. En esta guía, le mostraremos cómo desplegar un contrato de Primary Sale paso a paso usando cualquier moneda personalizada o existente para una tienda web que utiliza objetos del juego de un contrato ERC1155."},{"title":"Cómo hacer Primary Sales de objetos on-chain en Unity","img":"/images/guides/overview/unity-primary-sales.png","href":"/guias/unity-ventas-primarias","description":"Esta guía cubre la creación de una Primary Sale con el SDK de Unity de Sequence."}]},{"title":"Web3","cards":[{"title":"Cree un servicio escalable de minteo de NFT","img":"/images/guides/overview/sword.png","href":"/guias/mintear-coleccionables-serverless","description":"Aprovechando la Transaction API de Sequence y un entorno serverless, creará un servicio de minteo escalable para NFTs u otras transacciones que maneja automáticamente las complejidades de blockchain como reorganizaciones, gestión de nonce y paralelización de transacciones."},{"title":"Cree un marketplace personalizado con Sequence","img":"/images/guides/overview/marketplace.png","href":"/guias/mercado-personalizado","description":"Cree un marketplace impulsado por API donde los jugadores pueden mintear, vender o comprar objetos usando una interfaz web personalizada que utiliza las APIs de Orderbook de Sequence."},{"title":"API de Sequence Analytics en Dune","img":"/images/guides/analytics/dune-analytics.jpg","href":"/guias/analiticas-guia","description":"Guía para consultar información sobre el uso de sus usuarios para su proyecto específico utilizando un Cloudflare Worker serverless."},{"title":"Creación, almacenamiento y gestión de metadatos de coleccionables","img":"/images/guides/overview/storage.png","href":"/guias/metadata-guia","description":"Utilizando la Metadata API de Sequence, puede crear, gestionar y almacenar metadatos asociados a sus NFTs desde casi cualquier entorno. Le mostraremos cómo utilizar estas REST-APIs para organizar las colecciones de su juego o experiencia."}]}]} \ No newline at end of file +{"lastUpdated":"2025-07-09T10:28:35.469Z","totalCards":12,"sections":[{"title":"Desarrolladores de videojuegos","cards":[{"title":"Cree un juego móvil con Unity + Sequence","img":"/images/unity/jellyforest.webp","href":"/guias/jelly-forest-unity-guia","description":"Aprenda a crear un juego atractivo para iOS y Android que utiliza Sequence Embedded Wallets en segundo plano para habilitar un marketplace integrado y una moneda dentro del juego."},{"title":"Integre Sequence Wallets con una Mini-App de Telegram","img":"/images/guides/telegram/telegram-miniapp.webp","href":"/guias/integracion-telegram","description":"Siga nuestra guía de integración para aprender cómo integrar una Sequence Embedded Wallet en una aplicación de Telegram y así brindar soporte a sus usuarios en cadenas EVM."},{"title":"Cómo crear un juego con Unreal Engine","img":"/images/guides/overview/unreal-ew-guide.png","href":"/guias/unreal-ew-guia","description":"Utilice el SDK de Unreal de Sequence para mostrar información de Embedded Wallet, firmar mensajes y enviar transacciones."},{"title":"Cree un juego Dungeon Crawler con recompensas generadas por IA","img":"/images/guides/treasure-chest/dungeonMinter.png","href":"/guias/tesoro-guia","description":"Con este tutorial, cree un laberinto web donde los objetos de caja de recompensas (lootbox) se generan usando IA y se mintean dinámicamente en el wallet universal del jugador."},{"title":"Cree un juego WebGL en Typescript","img":"/images/aviator.png","href":"/guias/webgl-guia","description":"Siga una guía paso a paso para crear una demo de juego web que utiliza Sequence Embedded Wallet y tokens de logros personalizados dentro del juego."},{"title":"Venda objetos del juego a través de una tienda web","img":"/images/guides/overview/primary-sales.png","href":"/guias/ventas-primarias","description":"Impulse el crecimiento de su juego vendiendo objetos directamente a sus jugadores. En esta guía, le mostraremos cómo desplegar un contrato de Primary Sale paso a paso usando cualquier moneda personalizada o existente para una tienda web que utiliza objetos del juego de un contrato ERC1155."},{"title":"Cómo hacer Primary Sales de objetos on-chain en Unity","img":"/images/guides/overview/unity-primary-sales.png","href":"/guias/unity-ventas-primarias","description":"Esta guía cubre la creación de una Primary Sale con el SDK de Unity de Sequence."}]},{"title":"Web3","cards":[{"title":"Cree un servicio escalable de minteo de NFT","img":"/images/guides/overview/sword.png","href":"/guias/mintear-coleccionables-serverless","description":"Aprovechando la Transaction API de Sequence y un entorno serverless, creará un servicio de minteo escalable para NFTs u otras transacciones que maneja automáticamente las complejidades de blockchain como reorganizaciones, gestión de nonce y paralelización de transacciones."},{"title":"Use Privy con Sequence","img":"/images/guides/overview/privyLogo.png","href":"/guides/use-with-privy","description":"Aprende cómo conectar Privy con Sequence."},{"title":"Cree un marketplace personalizado con Sequence","img":"/images/guides/overview/marketplace.png","href":"/guias/mercado-personalizado","description":"Cree un marketplace impulsado por API donde los jugadores pueden mintear, vender o comprar objetos usando una interfaz web personalizada que utiliza las APIs de Orderbook de Sequence."},{"title":"API de Sequence Analytics en Dune","img":"/images/guides/analytics/dune-analytics.jpg","href":"/guias/analiticas-guia","description":"Guía para consultar información sobre el uso de sus usuarios para su proyecto específico utilizando un Cloudflare Worker serverless."},{"title":"Creación, almacenamiento y gestión de metadatos de coleccionables","img":"/images/guides/overview/storage.png","href":"/guias/metadata-guia","description":"Utilizando la Metadata API de Sequence, puede crear, gestionar y almacenar metadatos asociados a sus NFTs desde casi cualquier entorno. Le mostraremos cómo utilizar estas REST-APIs para organizar las colecciones de su juego o experiencia."}]}]} \ No newline at end of file diff --git a/es/guides/guide-overview.mdx b/es/guides/guide-overview.mdx index eb74a92f..d9c5241b 100644 --- a/es/guides/guide-overview.mdx +++ b/es/guides/guide-overview.mdx @@ -2,7 +2,7 @@ title: Guías description: Resumen de las guías para la infraestructura de Sequence para juegos web3. mode: wide -sidebarTitle: Resumen +sidebarTitle: Descripción general --- Siga nuestras guías paso a paso y utilice plantillas de código abierto para acelerar su salida al mercado. @@ -46,6 +46,10 @@ Siga nuestras guías paso a paso y utilice plantillas de código abierto para ac Aprovechando la Transaction API de Sequence y un entorno serverless, creará un servicio de minteo escalable para NFTs u otras transacciones que maneja automáticamente las complejidades de blockchain como reorganizaciones, gestión de nonce y paralelización de transacciones. + + Aprende cómo conectar Privy con Sequence. + + Cree un marketplace impulsado por API donde los jugadores pueden mintear, vender o comprar objetos usando una interfaz web personalizada que utiliza las APIs de Orderbook de Sequence. diff --git a/es/sdk/unity/power/wallet-ui.mdx b/es/sdk/unity/power/wallet-ui.mdx deleted file mode 100644 index 7ebb95d6..00000000 --- a/es/sdk/unity/power/wallet-ui.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Interfaz de Wallet ---- - -Como producto Wallet as a Service, este SDK no requiere interfaz de usuario. Sin embargo, como esperamos que algunas apps aún decidan ofrecer algún tipo de interfaz de wallet a los usuarios, estamos trabajando para brindar una UI predeterminada que puedes usar. - -Esta interfaz se encuentra bajo `SequenceFrontend`. - -Para agregar la UI a su escena, puede añadir el prefab `WalletPanel` que se encuentra en `SequenceFrontend > Prefabs`. Se abre usando el método Open; debe proporcionar un `Sequence.EmbeddedWallet.IWallet` (por ejemplo, un `SequenceWallet`) como argumento. - -Notarás que la interfaz aún está en desarrollo y también utiliza mocks en algunos lugares (como al obtener historial de transacciones y precios). Estos serán reemplazados en próximas actualizaciones del SDK, pero si quieres, puedes reemplazarlos tú mismo (¡y si quieres sumar puntos extra, envía un PR!). - -## Cómo Funciona -La interfaz de ejemplo de Sequence está compuesta por algunos componentes importantes. - -### UIPage -Un `UIPage` es la implementación base de una "página" en la interfaz de ejemplo. Ejemplos de páginas: `LoginPage`, `TokenInfoPage` - -Es responsable de abrir/cerrar la página y gestionar el `ITween` elegido. - -### ITween -Un `ITween` es una interfaz para una animación (entrada/salida) que se puede aplicar a un `RectTransform` (un componente requerido de un `UIPage`). Si no le gustan las animaciones de un `UIPage` o `UIPanel` puede cambiarlas fácilmente por cualquier otro MonoBehaviour que implemente la interfaz `ITween`, ¡incluso uno propio! - -Consejo: para ahorrar tiempo creando sus propias animaciones, considere usar el popular [conjunto de herramientas DOTween](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676) - -### UIPanel -Heredando de `UIPage`, un `UIPanel` es la implementación base de un "panel" en la interfaz de ejemplo. Ejemplos de paneles: `LoginPanel`, `WalletPanel` - -Además de las responsabilidades de `UIPage`, los UIPanels mantienen una pila de UIPages y `object[]` (argumentos de apertura) y se encargan de gestionar los event listeners requeridos y las transiciones de UI entre sus páginas hijas (según la jerarquía del inspector de la escena), incluyendo el manejo del botón "Atrás". - -### SequenceSampleUI -`SequenceSampleUI` puede considerarse el "administrador" de la interfaz de ejemplo. Mantiene una referencia a todos los UIPanels y se encarga de abrirlos según sea necesario, incluso en `Start()`. Si está integrando toda o parte de la interfaz de ejemplo en su proyecto, puede resultarle más práctico reemplazar `SequenceSampleUI` por su propio "administrador" de UI, usando `SequenceSampleUI` como referencia. - -## Personalización de la Interfaz -Construida sobre el sistema de UI de Unity, la interfaz es completamente personalizable. ¡Le animamos a adaptarla y "embellecerla" para que se ajuste a su aplicación! - -### Administrador de Esquema de Colores -Para ayudarle en el proceso, hemos agregado un script básico `ColorSchemeManager` que puede probar en nuestra escena Demo, la cual puede [importar vía Package Manager](/sdk/unity/installation#samples). - -Para usar el `ColorSchemeManager`, realice lo siguiente en modo edición. -1. Cree un [scriptable object](https://docs.unity3d.com/Manual/class-ScriptableObject.html) `Color Scheme` navegando a la barra superior `Assets > Create > Sequence > Color Scheme` y asígnele un nombre apropiado. -2. Configure los colores deseados en su nuevo scriptable object — ¡no olvide ajustar los valores alfa! Unity los pone en 0 por defecto. -3. Ubique el MonoBehaviour `ColorSchemeManager` en su escena en el inspector. En nuestra escena demo, esto está adjunto al gameObject `SequenceCanvas`. -4. Asigne su scriptable object recién creado como el Color Scheme. -5. Haga clic en "Apply". Tenga en cuenta que esto puede tardar unos segundos en aplicarse y puede que deba hacer clic en aplicar varias veces debido a cómo Unity refresca los cambios en el inspector (depende de la versión). - -El `ColorSchemeManager` no está pensado para darle resultados perfectos ni una interfaz perfecta desde el inicio, ¡pero esperamos que le ahorre algo de tiempo en el proceso! - -### Pronto habrá más herramientas de personalización -¡Esperamos brindarle más herramientas de conveniencia para ayudarle a personalizar la interfaz de usuario de manera más rápida y sencilla! Aunque tenemos nuestras propias ideas, nadie entiende sus necesidades mejor que usted, así que no dude en contactarnos con cualquier sugerencia que tenga. ¡O mejor aún, envíe un PR! \ No newline at end of file diff --git a/ja/guides/guide-cards.json b/ja/guides/guide-cards.json index d91a5239..47b02c5d 100644 --- a/ja/guides/guide-cards.json +++ b/ja/guides/guide-cards.json @@ -1 +1 @@ -{"lastUpdated":"2025-06-04T08:39:42.458Z","totalCards":11,"sections":[{"title":"ゲーム開発者向け","cards":[{"title":"UnityとSequenceでモバイルゲームを構築する","img":"/images/unity/jellyforest.webp","href":"/guides/jelly-forest-unity-guide","description":"Sequence Embedded Walletを内部的に活用し、統合マーケットプレイスやゲーム内通貨を備えた魅力的なiOS・Androidゲームの作り方を説明します。"},{"title":"Sequence WalletをTelegramミニアプリに統合する","img":"/images/guides/telegram/telegram-miniapp.webp","href":"/guides/telegram-integration","description":"統合ガイドに従い、TelegramアプリにSequence Embedded Walletを組み込んで、EVMチェーン上のユーザーをサポートする方法を学びましょう。"},{"title":"Unreal Engineでゲームを構築する方法","img":"/images/guides/overview/unreal-ew-guide.png","href":"/guides/unreal-ew-guide","description":"SequenceのUnreal SDKを使って、Embedded Walletの情報表示、メッセージ署名、トランザクション送信を行いましょう。"},{"title":"AI生成報酬付きダンジョンクローラーゲームを作成する","img":"/images/guides/treasure-chest/dungeonMinter.png","href":"/guides/treasure-chest-guide","description":"このチュートリアルでは、AIで生成されたガチャアイテムをプレイヤーのユニバーサルウォレットに動的にミントする、ウェブベースの迷路ゲームを作成します。"},{"title":"TypescriptでWebGLゲームを構築する","img":"/images/aviator.png","href":"/guides/webgl-guide","description":"Sequence Embedded Walletとカスタムのゲーム内実績トークンを活用したウェブゲームデモを、ステップバイステップで作成できます。"},{"title":"ウェブショップでゲームアイテムを販売する","img":"/images/guides/overview/primary-sales.png","href":"/guides/primary-sales","description":"ゲーム成長を加速させるため、アイテムをプレイヤーに直接販売できます。本ガイドでは、ERC1155コントラクト由来のゲームアイテムを利用したウェブショップで、任意のカスタム通貨や既存通貨を使ってPrimary Saleコントラクトをデプロイする手順を説明します。"},{"title":"UnityでオンチェーンアイテムのPrimary Saleを行う方法","img":"/images/guides/overview/unity-primary-sales.png","href":"/guides/unity-primary-sales","description":"このガイドでは、SequenceのUnity SDKを使ったPrimary Saleの作成方法を説明します。"}]},{"title":"Web3","cards":[{"title":"スケーラブルなNFTミンティングサービスを構築する","img":"/images/guides/overview/sword.png","href":"/guides/mint-collectibles-serverless","description":"SequenceのTransaction APIとサーバーレス環境を活用し、リオーグやノンス管理、トランザクションの並列処理など、ブロックチェーン特有の複雑さを自動で処理するスケーラブルなNFTミントサービスやその他トランザクションサービスを構築します。"},{"title":"Sequenceでカスタムマーケットプレイスを作成する","img":"/images/guides/overview/marketplace.png","href":"/guides/custom-marketplace","description":"Sequence Orderbook APIを活用し、プレイヤーがアイテムをミント・売買できるAPI駆動型のカスタムウェブマーケットプレイスを構築します。"},{"title":"DuneのSequence Analytics API","img":"/images/guides/analytics/dune-analytics.jpg","href":"/guides/analytics-guide","description":"Cloudflare Workerのサーバーレス環境を活用し、特定プロジェクトのユーザー利用状況を取得するための情報クエリガイドです。"},{"title":"コレクティブルのメタデータの作成、保存、管理","img":"/images/guides/overview/storage.png","href":"/guides/metadata-guide","description":"SequenceのMetadata APIを利用することで、NFTに関連するメタデータをほぼあらゆる環境からプログラム的に作成・管理・保存できます。これらのREST-APIを呼び出して、ゲームや体験のコレクションを整理する方法を解説します。"}]}]} \ No newline at end of file +{"lastUpdated":"2025-07-09T10:28:35.469Z","totalCards":12,"sections":[{"title":"ゲーム開発者向け","cards":[{"title":"UnityとSequenceでモバイルゲームを構築する","img":"/images/unity/jellyforest.webp","href":"/guides/jelly-forest-unity-guide","description":"Sequence Embedded Walletを内部的に活用し、統合マーケットプレイスやゲーム内通貨を備えた魅力的なiOS・Androidゲームの作り方を説明します。"},{"title":"Sequence WalletをTelegramミニアプリに統合する","img":"/images/guides/telegram/telegram-miniapp.webp","href":"/guides/telegram-integration","description":"統合ガイドに従い、TelegramアプリにSequence Embedded Walletを組み込んで、EVMチェーン上のユーザーをサポートする方法を学びましょう。"},{"title":"Unreal Engineでゲームを構築する方法","img":"/images/guides/overview/unreal-ew-guide.png","href":"/guides/unreal-ew-guide","description":"SequenceのUnreal SDKを使って、Embedded Walletの情報表示、メッセージ署名、トランザクション送信を行いましょう。"},{"title":"AI生成報酬付きダンジョンクローラーゲームを作成する","img":"/images/guides/treasure-chest/dungeonMinter.png","href":"/guides/treasure-chest-guide","description":"このチュートリアルでは、AIで生成されたガチャアイテムをプレイヤーのユニバーサルウォレットに動的にミントする、ウェブベースの迷路ゲームを作成します。"},{"title":"TypescriptでWebGLゲームを構築する","img":"/images/aviator.png","href":"/guides/webgl-guide","description":"Sequence Embedded Walletとカスタムのゲーム内実績トークンを活用したウェブゲームデモを、ステップバイステップで作成できます。"},{"title":"ウェブショップでゲームアイテムを販売する","img":"/images/guides/overview/primary-sales.png","href":"/guides/primary-sales","description":"ゲーム成長を加速させるため、アイテムをプレイヤーに直接販売できます。本ガイドでは、ERC1155コントラクト由来のゲームアイテムを利用したウェブショップで、任意のカスタム通貨や既存通貨を使ってPrimary Saleコントラクトをデプロイする手順を説明します。"},{"title":"UnityでオンチェーンアイテムのPrimary Saleを行う方法","img":"/images/guides/overview/unity-primary-sales.png","href":"/guides/unity-primary-sales","description":"このガイドでは、SequenceのUnity SDKを使ったPrimary Saleの作成方法を説明します。"}]},{"title":"Web3","cards":[{"title":"スケーラブルなNFTミンティングサービスを構築する","img":"/images/guides/overview/sword.png","href":"/guides/mint-collectibles-serverless","description":"SequenceのTransaction APIとサーバーレス環境を活用し、リオーグやノンス管理、トランザクションの並列処理など、ブロックチェーン特有の複雑さを自動で処理するスケーラブルなNFTミントサービスやその他トランザクションサービスを構築します。"},{"title":"PrivyをSequenceに接続する","img":"/images/guides/overview/privyLogo.png","href":"/guides/use-with-privy","description":"PrivyをSequenceに接続する方法"},{"title":"Sequenceでカスタムマーケットプレイスを作成する","img":"/images/guides/overview/marketplace.png","href":"/guides/custom-marketplace","description":"Sequence Orderbook APIを活用し、プレイヤーがアイテムをミント・売買できるAPI駆動型のカスタムウェブマーケットプレイスを構築します。"},{"title":"DuneのSequence Analytics API","img":"/images/guides/analytics/dune-analytics.jpg","href":"/guides/analytics-guide","description":"Cloudflare Workerのサーバーレス環境を活用し、特定プロジェクトのユーザー利用状況を取得するための情報クエリガイドです。"},{"title":"コレクティブルのメタデータの作成、保存、管理","img":"/images/guides/overview/storage.png","href":"/guides/metadata-guide","description":"SequenceのMetadata APIを利用することで、NFTに関連するメタデータをほぼあらゆる環境からプログラム的に作成・管理・保存できます。これらのREST-APIを呼び出して、ゲームや体験のコレクションを整理する方法を解説します。"}]}]} \ No newline at end of file diff --git a/ja/guides/guide-overview.mdx b/ja/guides/guide-overview.mdx index ec45dc4b..bb5d7a63 100644 --- a/ja/guides/guide-overview.mdx +++ b/ja/guides/guide-overview.mdx @@ -46,6 +46,10 @@ sidebarTitle: 概要 SequenceのTransaction APIとサーバーレス環境を活用し、リオーグやノンス管理、トランザクションの並列処理など、ブロックチェーン特有の複雑さを自動で処理するスケーラブルなNFTミントサービスやその他トランザクションサービスを構築します。
+ + PrivyをSequenceに接続する方法 + + Sequence Orderbook APIを活用し、プレイヤーがアイテムをミント・売買できるAPI駆動型のカスタムウェブマーケットプレイスを構築します。 diff --git a/ja/sdk/unity/power/wallet-ui.mdx b/ja/sdk/unity/power/wallet-ui.mdx deleted file mode 100644 index d7589156..00000000 --- a/ja/sdk/unity/power/wallet-ui.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: ウォレットUI ---- - -Wallet as a Service製品として、このSDKにはUIは不要です。ただし、一部のアプリではユーザー向けにウォレットUIを提供したい場合もあると考え、デフォルトUIの提供に取り組んでいます。 - -このUIは `SequenceFrontend` に含まれています。 - -シーンにUIを追加するには、`SequenceFrontend > Prefabs` 内にある `WalletPanel` プレハブを追加してください。開く際は Open メソッドを使用し、引数として `Sequence.EmbeddedWallet.IWallet`(例:`SequenceWallet`)を指定する必要があります。 - -UIはまだ開発途中であり、一部の箇所ではモック(トランザクション履歴や価格の取得など)が使われていることにお気づきになるでしょう。これらは今後のSDKアップデートで置き換えられる予定ですが、ご自身で差し替えていただいても構いません(もしPRを送っていただければ大歓迎です!)。 - -## 仕組みについて -サンプルのSequence UIはいくつかの重要なコンポーネントで構成されています。 - -### UIPage -`UIPage`は、サンプルUIにおける「ページ」の基本実装です。例:`LoginPage`、`TokenInfoPage` - -ページの開閉や、選択した`ITween`の管理を担当します。 - -### ITween -`ITween`はアニメーション(イン/アウト)用のインターフェースで、`RectTransform`(`UIPage`に必須のコンポーネント)に適用できます。特定の`UIPage`や`UIPanel`のアニメーションが気に入らない場合は、`ITween`インターフェースを実装した他のMonoBehaviour(ご自身で作成したものも含む)に簡単に差し替え可能です。 - -ヒント:独自のアニメーションを作成する時間を節約したい場合は、人気のある[DOTweenツールセット](https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676)の利用を検討してください。 - -### UIPanel -`UIPage`を継承した`UIPanel`は、サンプルUIにおける「パネル」の基本実装です。例:`LoginPanel`、`WalletPanel` - -`UIPage`の役割に加え、UIPanelはUIPagesと`object[]`(開く際の引数)のスタックを管理し、必要なイベントリスナーや子ページ間のUI遷移(Sceneインスペクタの階層に従う)、「戻る」ボタンの処理なども担当します。 - -### SequenceSampleUI -`SequenceSampleUI`はサンプルUIの「マネージャー」として機能します。すべてのUIPanelへの参照を保持し、必要に応じて(`Start()`時など)開く役割を担います。サンプルUIの全部または一部をプロジェクトに組み込む場合は、`SequenceSampleUI`を参考にしつつ、ご自身のUI「マネージャー」と置き換えるのが実用的かもしれません。 - -## UIのカスタマイズ性 -UnityのUIシステム上に構築されているため、UIは完全にカスタマイズ可能です。ぜひご自身のアプリに合わせて自由に「美しく」仕上げてください! - -### カラースキームマネージャー -その手助けとして、基本的な`ColorSchemeManager`スクリプトを用意しました。デモシーンでお試しいただけます([Package Managerからインポート可能](/sdk/unity/installation#samples))。 - -`ColorSchemeManager`を使うには、編集モードで以下の手順を行ってください。 -1. 上部メニューの `Assets > Create > Sequence > Color Scheme` から `Color Scheme` [スクリプタブルオブジェクト](https://docs.unity3d.com/Manual/class-ScriptableObject.html) を作成し、適切な名前を付けてください。 -2. 新しく作成したスクリプタブルオブジェクトで希望の色を設定します(アルファ値の設定もお忘れなく!Unityではデフォルトで0になっています)。 -3. インスペクタでシーン内の`ColorSchemeManager` MonoBehaviourを探してください。デモシーンでは、これは`SequenceCanvas`ゲームオブジェクトにアタッチされています。 -4. 作成したスクリプタブルオブジェクトをカラースキームとして設定します。 -5. 「Apply」をクリックしてください。適用には数秒かかる場合があり、Unityのインスペクタ更新の仕組み(バージョンによる)により、複数回クリックが必要な場合もあります。 - -`ColorSchemeManager`は完璧な結果や理想的なUIをすぐに提供するものではありませんが、作業時間の短縮に役立てば幸いです。 - -### さらなるカスタマイズツールも近日公開予定 -今後、より便利なツールを提供し、UIのカスタマイズをさらに簡単・迅速にできるようにしたいと考えています。私たちにもアイデアはありますが、皆さまご自身のニーズが一番よく分かるはずです。ご要望やアイデアがあれば、ぜひお気軽にご連絡ください。もちろん、PRのご提出も大歓迎です! \ No newline at end of file