Con Sequence, puedes crear un smart contract wallet que tu servidor puede usar para enviar transacciones para tus usuarios sin tener que preocuparte por la velocidad de las transacciones, el rendimiento o los reorgs.

La única diferencia respecto al uso de un wallet Sequence típico al enviar transacciones a la blockchain, es que a nivel de smart contract el msg.sender es una de las direcciones de wallet de Sequence Relayers. Para los contratos estándar de Sequence Builder, esto no es un problema cuando se combina con una solicitud a la Transactions API de relayed transactions.

Por defecto, las transacciones de Sequence se ejecutan de forma secuencial.

Los siguientes pasos te guiarán para crear tu servidor y mintear coleccionables a una dirección de wallet:

  1. Configuración del entorno con Express Server: Cree un servidor basado en NodeJs usando la librería Express para aceptar solicitudes HTTP
  2. Gestión de proyecto y clave de acceso: Solicite una clave de acceso pública para interactuar con el stack de Sequence
  3. Despliegue del contrato de coleccionables: Despliegue un contrato de coleccionables para poder enviar transacciones a la blockchain y mintear tokens a una dirección de wallet
  4. Construcción de un relayer patrocinado con la Transactions API: Cree una función para usar en una ruta de Express que llame a la Transactions API de Sequence desde un contrato patrocinado

Funciones adicionales:

  • (Opcional) Retransmisión con moneda propia de la wallet: Crea una función para usar en una ruta de Express que llame a la Transactions API de Sequence y pague usando una moneda que posee la wallet
  • (Opcional) Retransmisión de transacciones en paralelo: Realiza agrupación de transacciones para enviar una moneda
1

Configuración del entorno con Express Server

Asegúrate de tener instalado pnpm (u otro gestor de paquetes de Node) con el siguiente comando:

curl -fsSL https://get.pnpm.io/install.sh | sh -

Luego, clone el siguiente código de plantilla de express

Express es un framework minimalista y flexible para aplicaciones web en Node.js que ofrece un conjunto robusto de funciones para aplicaciones web y móviles, y será utilizado en esta guía.

Una vez que el código esté en su máquina local, ejecute su servidor y cliente con el siguiente comando:

pnpm run start

Dentro del código hay una ruta llamada /mint que puede llamarse desde la CLI para pruebas.

Pruebe con este ejemplo de solicitud curl:

curl -X POST http://localhost:3000/mint -d '{"tokenID": 0, "address": "0x"}'

Debería ver la siguiente salida:

{"txHash":"0x"}
2

Gestión de proyecto y clave de acceso

Primero, siga esta guía para aprender cómo registrarse en Sequence Builder y cómo crear un proyecto.

Luego, para usar la Transactions API, deberá actualizar su facturación a Developer, lo cual puede hacer siguiendo esta guía.

Finalmente, se requiere una Public Access Key para la Transactions API, que puede obtener siguiendo esta guía.

Por último, actualice el archivo .env.example a .env con lo siguiente:

CHAIN_HANDLE='<CHAIN_HANDLE>' # e.g. `mainnet`, `xr-sepolia`, etc.

PROJECT_ACCESS_KEY='<PUBlIC_ACCESS_KEY>'
3

Despliegue del contrato de coleccionables

Siga esta guía para desplegar un contrato de coleccionables.

Si está usando una red que no es testnet y necesita patrocinar su contrato, puede hacerlo siguiendo esta guía

Finalmente, actualice el archivo .env con su contrato de coleccionables desplegado:

...

COLLECTIBLE_CONTRACT_ADDRESS="<ADDRESS>"

Construya un relayer patrocinado con la Transactions API

El código completo para esta sección se encuentra aquí

Primero, usando el código de plantilla proporcionado en el paso #1, necesitaremos agregar algunos paquetes

import { Session } from '@0xsequence/auth'

import { findSupportedNetwork, NetworkConfig } from '@0xsequence/network'

Luego, su servidor necesitará una wallet EOA que pueda firmar mensajes. Será la propietaria de su wallet Sequence del lado del servidor, la cual se usará para enviar transacciones.

Abrir una sesión puede desencadenar una migración de su wallet Sequence a una nueva versión, esto podría ser de v1 a v2 o de v2 a versiones futuras.

La migración es un proceso irreversible; una vez que su wallet es migrada, no puede volver a una versión anterior.

Para detectar cualquier migración no deseada, puede usar el callback onMigration.

Para implementar la función callContract, incluya el siguiente código que utiliza un único firmante para retransmitir transacciones:

const callContract = async (address: string, tokenID: number): Promise<ethers.providers.TransactionResponse> => {

	

	const chainConfig: NetworkConfig = findSupportedNetwork(process.env.CHAIN_HANDLE!)!

	const provider = new ethers.providers.StaticJsonRpcProvider({

		url: chainConfig.rpcUrl

	})



	const walletEOA = new ethers.Wallet(process.env.PKEY!, provider);

	const relayerUrl = `https://${chainConfig.name}-relayer.sequence.app`



	// Create a single signer sequence wallet session

	const session = await Session.singleSigner({

		signer: walletEOA,

		projectAccessKey: process.env.PROJECT_ACCESS_KEY!

	})



	const signer = session.account.getSigner(chainConfig.chainId)

	

	// Standard interface for ERC1155 contract deployed via Sequence Builder

	const collectibleInterface = new ethers.Interface([

		'function mint(address to, uint256 tokenId, uint256 amount, bytes data)'

	])

		

	const data = collectibleInterface.encodeFunctionData(

		'mint', [`${address}`, `${tokenID}`, "1", "0x00"]

	)



	const txn = {

		to: process.env.COLLECTIBLE_CONTRACT_ADDRESS, 

		data: data

	}



	try {

		return await signer.sendTransaction(txn)

	} catch (err) {

		console.error(`ERROR: ${err}`)

		throw err

	}

}

Por último, actualice el archivo .env con una clave privada para una wallet que puede generarse desde esta aplicación (solo para fines de demostración). Para producción, recomendamos generar claves privadas de forma segura y local en su computadora usando este script de ejemplo.

Luego, actualice la variable PKEY con la clave:

...

PKEY='<WALLET_PRIVATE_KEY>'

Otorgar el rol MINTER_ROLE a la dirección de la wallet relayer

Debe actualizar el acceso por roles del contrato en el Builder para que solo reciba solicitudes desde la dirección de la wallet minter.

Puede hacer esto en Sequence Builder otorgando el minter permission a su Sequence Wallet Transactions API Address.

Para hacerlo, abra su proyecto, navegue a la página de Contracts, seleccione sus Linked contracts y, en la pestaña Write Contract, expanda el método grantRole.

Complete con los siguientes datos:

bytes32 role: 0x9f2df0fed2c77648de5860a4cc508cd0818c85b8b8a1ab4ceeef8d981c8956a6

address account: <Generated Sequence Transactions API Wallet Address>

Donde el valor de role ingresado es el resultado de keccak256("MINTER_ROLE") en solidity o ethers.solidityPackedKeccak256(ethers.toUtf8Bytes("MINTER_ROLE")) en javascript

Esto hace que solo su dirección específica pueda mintear desde el contrato; de lo contrario, dará error.

Complete la actualización del rol haciendo clic en write y firme la transacción patrocinada.

Su aplicación ya está lista para que envíe una transacción de prueba desde el frontend del cliente iniciando sesión en su wallet y haciendo clic en mintear.

¡Pruébelo!

No olvide actualizar la clave de acceso en el cliente en la función initWallet

(Opcional) Relevo con moneda propia de la wallet

También puede forzar una forma específica de pagar las tarifas de gas:

import { Session } from '@0xsequence/auth'

import { ethers } from 'ethers'



// where the <chain_handle> corresponds to https://docs.sequence.xyzhttps://status.sequence.info/

const provider = new ethers.providers.JsonRpcProvider('https://nodes.sequence.app/<chain_handle>');



// Create your server EOA

const walletEOA = new ethers.Wallet(serverPrivateKey, provider)



// Open a Sequence session, this will find or create

// a Sequence wallet controlled by your server EOA

const session = await Session.singleSigner({

  signer: walletEOA,

  projectAccessKey: '<access_key>'

  // OPTIONAL: Multiple wallets could be found for the same EOA

  // to enforce a specific wallet you can use the following callback

  selectWallet: async (wallets: string[]) => {

    const found = wallets.find(w => w === EXPECTED_WALLET_ADDRESS)

    if (!found) throw Error('wallet not found')

    // Returning the wallet address will make the session use it

    // returning undefined will make the session create a new wallet

    return found

  }

})



const signer = session.account.getSigner(137, {

  // OPTIONAL: You can also enforce a specific way to pay for gas fees

  // if not provided the sdk will select one for you

  selectFee: async (

    _txs: any,

    options: FeeOption[]

  ) => {

    // Find the option to pay with native tokens

    const found = options.find(o => !o.token.contractAddress)

    if (!found) throw Error('fee option not found')

    return found

  }

})



// Initialize the contract

const usdc = new ethers.Contract(

  '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', // USDC on Polygon

  ERC_20_ABI,

  signer

)



// Send the transaction

const txnResponse = await usdc.transfer(recipient, 1)



// Check if transaction was successful

if (txnReceipt.status != 1) {

  console.log(`Unexpected status: ${txnReceipt.status}`)

}

(Opcional) Relevo de transacciones en paralelo

Si desea enviar múltiples transacciones independientes sin necesidad de agruparlas, también puede enviarlas en espacios de nonce distintos.

Usar espacios de nonce distintos para sus transacciones le indica a la Transactions API que no hay dependencia entre ellas y que pueden ejecutarse en la blockchain en cualquier orden.

Esto permite que las transacciones se envíen de inmediato, sin búfer, sin tener que esperar a un lote completo.

Aquí hay un ejemplo de cómo hacerlo:

// Generate random nonce spaces with ~0% probability of collision

const randomNonceSpace1 = ethers.BigNumber.from(

  ethers.hexlify(ethers.randomBytes(20))

);

const randomNonceSpace2 = ethers.BigNumber.from(

  ethers.hexlify(ethers.randomBytes(20))

);



// Create signers for each nonce space

const signer1 = session.account.getSigner(137, {

  nonceSpace: randomNonceSpace1,

});



const signer2 = session.account.getSigner(137, {

  nonceSpace: randomNonceSpace2,

});



// Generate transactions

const txn1 = {

  to: tokenContract.address,

  data: erc20Interface.encodeFunctionData("transfer", [recipient1, amount1]),

};



const txn2 = {

  to: tokenContract.address,

  data: erc20Interface.encodeFunctionData("transfer", [recipient2, amount2]),

};



// Dispatch transactions, which can now be executed in parallel

await Promise.all([

  signer1.sendTransaction(txn1),

  signer2.sendTransaction(txn2),

]);