Skip to main content

Guía - Trigger con NextJS

Cómo Funciona Sherry - Entendiendo el Flujo

Antes de empezar a construir, es importante entender cómo Sherry Links conecta usuarios, plataformas y tu backend de mini app. El siguiente diagrama ilustra el flujo completo:

Diagrama de Flujo de Sherry Links - Cómo los usuarios interactúan con mini apps a través de la plataforma

Flujo Paso a Paso

  1. Sherry hace petición GET y recibe metadata

    • La plataforma descubre tu mini app y solicita su configuración
    • Tu backend responde con metadata que define la UI y funcionalidad
  2. Back-End retorna metadata

    • La metadata incluye campos de formulario, botones de acción, descripciones y endpoints
    • Esto le dice a Sherry cómo renderizar la interfaz de tu mini app
  3. Sherry renderiza mini-app

    • La plataforma crea un formulario amigable basado en tu metadata
    • Los usuarios pueden ver campos de entrada, descripciones y botones de acción
  4. Usuario ejecuta mini-app

    • El usuario llena el formulario con sus parámetros deseados
    • El usuario hace clic en el botón de acción para enviar su solicitud
  5. Sherry hace petición POST y recibe transacción serializada desde Back-End

    • La plataforma envía la entrada del usuario a tu backend para procesamiento
    • Tu backend aplica lógica personalizada y retorna una transacción blockchain lista para firmar
  6. Usuario confirma transacción

    • El usuario revisa los detalles de la transacción en su wallet
    • El usuario firma y envía la transacción a la blockchain
Concepto Clave

Tu mini app actúa como una fábrica inteligente de transacciones - toma la entrada del usuario, aplica tu lógica de negocio única, y produce transacciones blockchain listas para firmar. Esto es lo que hace poderoso a Sherry Links: combinar gran UX con funcionalidad blockchain personalizada.

Requisitos Previos

Antes de comenzar, asegúrate de tener lo siguiente instalado y configurado:

  • Node.js: Versión 18.x o superior
  • Gestor de Paquetes: npm, yarn, o pnpm
  • Conocimiento del Framework: Conceptos básicos de Next.js y TypeScript
  • Conceptos Básicos de Blockchain: Entendimiento de contratos inteligentes y ABI

Configuración Inicial

1. Crear Proyecto Next.js

npx create-next-app@latest mi-sherry-app --typescript --eslint --tailwind --src-dir --app --import-alias "@/*"
cd mi-sherry-app

2. Instalar Dependencias

npm install @sherrylinks/sdk viem wagmi

3. Configurar Next.js (Opcional)

Optimización de Build

Para evitar errores de build con ESLint, puedes deshabilitarlo en next.config.js:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
};

module.exports = nextConfig;

Creando el Endpoint GET - Metadata

El endpoint GET es el corazón de tu mini app. Aquí defines toda la información y estructura que las plataformas necesitan para renderizar tu aplicación.

Entendiendo la Metadata

La metadata le dice a las plataformas cómo renderizar tu mini app, qué inputs mostrar a los usuarios, y dónde enviar los datos cuando los usuarios interactúan con ella.

1. Crear el Archivo de Ruta

Crea el archivo app/api/mi-app/route.ts:

app/api/mi-app/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { createMetadata, Metadata, ValidatedMetadata } from '@sherrylinks/sdk';

2. Configurar la Estructura Básica del Handler GET

export async function GET(req: NextRequest) {
try {
// Obtener información de la URL del servidor
const host = req.headers.get('host') || 'localhost:3000';
const protocol = req.headers.get('x-forwarded-proto') || 'http';

// Construir la URL base
const serverUrl = `${protocol}://${host}`;

// Construiremos el objeto metadata paso a paso abajo
} catch (error) {
console.error('Error creando metadata:', error);
return NextResponse.json({ error: 'Error al crear metadata' }, { status: 500 });
}
}
¿Por qué serverUrl?

Esto detecta automáticamente si estás ejecutando localmente (http://localhost:3000) o en producción (https://tudominio.com) y construye la URL base correcta para tu mini app.

3. Definir Información Básica de la App

export async function GET(req: NextRequest) {
try {
const host = req.headers.get('host') || 'localhost:3000';
const protocol = req.headers.get('x-forwarded-proto') || 'http';
const serverUrl = `${protocol}://${host}`;

const metadata: Metadata = {
url: 'https://sherry.social',
icon: 'https://avatars.githubusercontent.com/u/117962315',
title: 'Mensaje con Timestamp',
baseUrl: serverUrl,
description:
'Almacena un mensaje con un timestamp optimizado calculado por nuestro algoritmo',
// Las acciones se agregarán en el siguiente paso
};
} catch (error) {
// Manejo de errores...
}
}
Entendiendo Cada Campo
CampoDescripciónEjemplo
urlURL principal del sitio web de tu proyecto"https://sherry.social"
iconURL de imagen públicamente accesible (200x200px recomendado)"https://ejemplo.com/icon.png"
titleNombre corto y descriptivo para tu mini app"Mensaje con Timestamp"
baseUrlURL del servidor donde tu mini app está alojada (auto-detectada)"https://tudominio.com"
descriptionExplicación clara de qué hace tu mini app"Almacenar mensajes con timestamps optimizados"

4. Agregar Array de Acciones

Las Acciones Definen la Funcionalidad

Las acciones definen qué pueden hacer los usuarios con tu mini app. Cada acción representa un botón con funcionalidad específica.

const metadata: Metadata = {
url: 'https://sherry.social',
icon: 'https://avatars.githubusercontent.com/u/117962315',
title: 'Mensaje con Timestamp',
baseUrl: serverUrl,
description: 'Almacena un mensaje con un timestamp optimizado calculado por nuestro algoritmo',
actions: [
{
type: 'dynamic',
label: 'Almacenar Mensaje',
description:
'Almacena tu mensaje con un timestamp personalizado calculado para almacenamiento óptimo',
chains: { source: 43113 },
path: `/api/mi-app`,
// Los parámetros se agregarán en el siguiente paso
},
],
};
Entendiendo las Propiedades de Acción
  • type: Siempre usa "dynamic" para mini apps complejas que necesitan lógica personalizada
  • label: El texto que aparecerá en el botón de acción
  • description: Explicación de qué hace esta acción específica
  • chains.source: La blockchain donde se ejecutará la transacción (chain ID)
    • 43113 = Avalanche Fuji Testnet
    • 43114 = Avalanche Mainnet
  • path: El endpoint de API que manejará la petición POST

5. Configurar Parámetros de Entrada del Usuario

const metadata: Metadata = {
url: 'https://sherry.social',
icon: 'https://avatars.githubusercontent.com/u/117962315',
title: 'Mensaje con Timestamp',
baseUrl: serverUrl,
description: 'Almacena un mensaje con un timestamp optimizado calculado por nuestro algoritmo',
actions: [
{
type: 'dynamic',
label: 'Almacenar Mensaje',
description:
'Almacena tu mensaje con un timestamp personalizado calculado para almacenamiento óptimo',
chains: { source: 43113 },
path: `/api/mi-app`,
params: [
{
name: 'mensaje',
label: 'Tu Mensaje',
type: 'text',
required: true,
description: 'Ingresa el mensaje que quieres almacenar en la blockchain',
},
],
},
],
};
Tipos de Parámetros Disponibles
  • "text": Entrada de texto de una línea - "textarea": Entrada de texto multilínea - "number": Entrada numérica con validación - "email": Entrada de email con validación - "url": Entrada de URL con validación

6. Validar y Retornar la Metadata

export async function GET(req: NextRequest) {
try {
const host = req.headers.get('host') || 'localhost:3000';
const protocol = req.headers.get('x-forwarded-proto') || 'http';
const serverUrl = `${protocol}://${host}`;

const metadata: Metadata = {
url: 'https://sherry.social',
icon: 'https://avatars.githubusercontent.com/u/117962315',
title: 'Mensaje con Timestamp',
baseUrl: serverUrl,
description:
'Almacena un mensaje con un timestamp optimizado calculado por nuestro algoritmo',
actions: [
{
type: 'dynamic',
label: 'Almacenar Mensaje',
description:
'Almacena tu mensaje con un timestamp personalizado calculado para almacenamiento óptimo',
chains: { source: 43113 },
path: `/api/mi-app`,
params: [
{
name: 'mensaje',
label: 'Tu Mensaje',
type: 'text',
required: true,
description: 'Ingresa el mensaje que quieres almacenar en la blockchain',
},
],
},
],
};

// Validar metadata usando el SDK
const validated: ValidatedMetadata = createMetadata(metadata);

// Retornar con headers CORS para acceso cross-origin
return NextResponse.json(validated, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
},
});
} catch (error) {
console.error('Error creando metadata:', error);
return NextResponse.json({ error: 'Error al crear metadata' }, { status: 500 });
}
}
Beneficios de la Validación

La función createMetadata() valida la estructura de tu metadata y asegura que cumple con los requisitos del Sherry SDK antes de retornarla a la plataforma.

Ejemplo 1: Transferencia Simple (Solo Aprendizaje)

Nota Importante

Este ejemplo es para aprender los conceptos básicos solamente. Transferencias simples como esta ya están soportadas por nuestro TransferAction incorporado y NO serán aceptadas en envíos al minithon. Úsalo para entender el flujo, luego pasa al Ejemplo 2 para funcionalidad lista para minithon.

Entendiendo el Flujo de Petición POST

Cuando un usuario completa el formulario de tu mini app y hace clic en el botón de acción, la plataforma enviará una petición POST a tu endpoint con la entrada del usuario como parámetros de URL.

1. Configurar la Estructura del Handler POST

Agrega estas importaciones al inicio de tu archivo:

app/api/mi-app/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { avalancheFuji } from 'viem/chains';
import { createMetadata, Metadata, ValidatedMetadata, ExecutionResponse } from '@sherrylinks/sdk';
import { serialize } from 'wagmi';

Ahora construyamos el handler POST:

export async function POST(req: NextRequest) {
try {
// Paso 1: Extraer parámetros de la URL
const { searchParams } = new URL(req.url);
const mensaje = searchParams.get('mensaje');

// Agregaremos validación y creación de transacción abajo
} catch (error) {
console.error('Error en petición POST:', error);
return NextResponse.json({ error: 'Error Interno del Servidor' }, { status: 500 });
}
}

2. Crear el Objeto de Transacción

// Crear una transacción de transferencia simple
const tx = {
to: '0x5ee75a1B1648C023e885E58bD3735Ae273f2cc52',
value: BigInt(1000000), // 1000000 wei = 0.000001 AVAX
chainId: avalancheFuji.id,
};
Entendiendo las Propiedades de Transacción
  • to: La dirección de destino que recibirá la transferencia
  • value: La cantidad a transferir en wei (unidad más pequeña). Usa BigInt() para números grandes
  • chainId: El ID numérico de la blockchain (avalancheFuji.id = 43113)

3. Ejemplo Completo de Transferencia Simple

Handler POST Completo - Transferencia Simple
export async function POST(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const mensaje = searchParams.get('mensaje');

if (!mensaje) {
return NextResponse.json(
{ error: 'El parámetro mensaje es requerido' },
{
status: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
},
);
}

const tx = {
to: '0x5ee75a1B1648C023e885E58bD3735Ae273f2cc52',
value: BigInt(1000000),
chainId: avalancheFuji.id,
};

// Serializar la transacción para la blockchain
const serialized = serialize(tx);

// Crear el objeto de respuesta que Sherry espera
const resp: ExecutionResponse = {
serializedTransaction: serialized,
chainId: avalancheFuji.id, // Usar el ID de la chain
};

// Retornar la respuesta con headers CORS
return NextResponse.json(resp, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
} catch (error) {
console.error('Error en petición POST:', error);
return NextResponse.json({ error: 'Error Interno del Servidor' }, { status: 500 });
}
}
Por Qué Esto No Es Listo para Minithon

Este ejemplo solo realiza una transferencia simple de tokens. Dado que nuestra plataforma ya proporciona TransferAction para esta funcionalidad exacta, los envíos al minithon necesitan demostrar funcionalidad más compleja y con valor agregado.

Ejemplo 2: Interacción con Contratos Inteligentes (Listo para Minithon)

Adecuado para Minithon

Este ejemplo demuestra interacción con contratos inteligentes con lógica de negocio personalizada, que es lo que buscamos en los envíos al minithon.

1. Entendiendo el Contrato Inteligente

Primero, entendamos qué hace nuestro contrato inteligente:

Contrato TimestampedMessage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/**
* @title TimestampedMessage
* @dev Stores messages with an optimized timestamp
*/
contract TimestampedMessage {
event MessageStored(address indexed sender, string message, uint256 timestamp, uint256 optimizedTimestamp);

struct MessageData {
address sender;
string message;
uint256 timestamp;
uint256 optimizedTimestamp;
}

// Array to store all messages
MessageData[] public messages;

// Mapping from address to their message count
mapping(address => uint256) public userMessageCount;

/**
* @dev Store a message with an optimized timestamp
* @param message The message to store
* @param optimizedTimestamp A timestamp calculated off-chain
*/
function storeMessage(string memory message, uint256 optimizedTimestamp) public {
// Store message with the current block timestamp and the optimized timestamp
messages.push(MessageData({
sender: msg.sender,
message: message,
timestamp: block.timestamp,
optimizedTimestamp: optimizedTimestamp
}));

// Increment message count for the sender
userMessageCount[msg.sender]++;

// Emit event
emit MessageStored(msg.sender, message, block.timestamp, optimizedTimestamp);
}

/**
* @dev Get the count of all messages
*/
function getMessageCount() public view returns (uint256) {
return messages.length;
}

/**
* @dev Get a message by index
*/
function getMessage(uint256 index) public view returns (
address sender,
string memory message,
uint256 timestamp,
uint256 optimizedTimestamp
) {
require(index < messages.length, "Index out of bounds");
MessageData memory data = messages[index];
return (data.sender, data.message, data.timestamp, data.optimizedTimestamp);
}

/**
* @dev Get all messages from a specific sender
*/
function getMessagesBySender(address sender) public view returns (
string[] memory messageTexts,
uint256[] memory timestamps,
uint256[] memory optimizedTimestamps
) {
uint256 count = userMessageCount[sender];

messageTexts = new string[](count);
timestamps = new uint256[](count);
optimizedTimestamps = new uint256[](count);

uint256 currentIndex = 0;

for (uint256 i = 0; i < messages.length; i++) {
if (messages[i].sender == sender) {
messageTexts[currentIndex] = messages[i].message;
timestamps[currentIndex] = messages[i].timestamp;
optimizedTimestamps[currentIndex] = messages[i].optimizedTimestamp;
currentIndex++;
}
}

return (messageTexts, timestamps, optimizedTimestamps);
}
}
Por Qué Esto Es Listo para Minithon
  • Lógica de negocio personalizada (cálculo de timestamp optimizado) - Gestión de estado de contrato inteligente - Emisión de eventos para seguimiento - Estructuras de datos complejas - Va más allá de transferencias simples

2. Configurar el ABI del Contrato

Paso Crucial

El ABI (Application Binary Interface) es esencial para interactuar con contratos inteligentes. Define las funciones disponibles, sus parámetros y tipos de retorno. Sin el ABI correcto, no podrás codificar las llamadas a funciones del contrato.

Crea el archivo blockchain/abi.ts en tu proyecto:

blockchain/abi.ts
export const abi = [
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'sender',
type: 'address',
},
{
indexed: false,
internalType: 'string',
name: 'message',
type: 'string',
},
{
indexed: false,
internalType: 'uint256',
name: 'timestamp',
type: 'uint256',
},
{
indexed: false,
internalType: 'uint256',
name: 'optimizedTimestamp',
type: 'uint256',
},
],
name: 'MessageStored',
type: 'event',
},
{
inputs: [
{
internalType: 'string',
name: 'message',
type: 'string',
},
{
internalType: 'uint256',
name: 'optimizedTimestamp',
type: 'uint256',
},
],
name: 'storeMessage',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'index',
type: 'uint256',
},
],
name: 'getMessage',
outputs: [
{
internalType: 'address',
name: 'sender',
type: 'address',
},
{
internalType: 'string',
name: 'message',
type: 'string',
},
{
internalType: 'uint256',
name: 'timestamp',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'optimizedTimestamp',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getMessageCount',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'sender',
type: 'address',
},
],
name: 'getMessagesBySender',
outputs: [
{
internalType: 'string[]',
name: 'messageTexts',
type: 'string[]',
},
{
internalType: 'uint256[]',
name: 'timestamps',
type: 'uint256[]',
},
{
internalType: 'uint256[]',
name: 'optimizedTimestamps',
type: 'uint256[]',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
name: 'messages',
outputs: [
{
internalType: 'address',
name: 'sender',
type: 'address',
},
{
internalType: 'string',
name: 'message',
type: 'string',
},
{
internalType: 'uint256',
name: 'timestamp',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'optimizedTimestamp',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
name: 'userMessageCount',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
] as const;
Cómo Obtener el ABI
  • Remix: Después de compilar, ve a la pestaña solidity compilery busca el texto ABI al final.
  • Hardhat: Se genera automáticamente en artifacts/contracts/
  • Foundry: Usa forge inspect <ContractName> abi
  • Explorador de Blockchain: Si el contrato está verificado, puedes copiar el ABI

3. Actualizar las Importaciones de tu Archivo de Ruta

app/api/mi-app/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { avalancheFuji } from 'viem/chains';
import { createMetadata, Metadata, ValidatedMetadata, ExecutionResponse } from '@sherrylinks/sdk';
import { serialize } from 'wagmi';
import { encodeFunctionData, TransactionSerializable } from 'viem';
import { abi } from '@/blockchain/abi';

// Dirección del contrato en Avalanche Fuji Testnet
const CONTRACT_ADDRESS = '0xTuContratoInteligenteDirecciónAquí';

4. Construir el Handler POST del Contrato Inteligente

export async function POST(req: NextRequest) {
try {
// Extraer entrada del usuario
const { searchParams } = new URL(req.url);
const message = searchParams.get('mensaje');

// Validar parámetros requeridos
if (!message) {
return NextResponse.json(
{ error: 'Message parameter is required' },
{
status: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
},
);
}

// La lógica personalizada y creación de transacción se agregarán abajo
} catch (error) {
console.error('Error en petición POST:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
Entendiendo las Propiedades de Transacción de Contrato Inteligente
  • to: La dirección del contrato desplegado en la blockchain
  • data: Los datos codificados de la función usando encodeFunctionData
  • chainId: El ID numérico de la blockchain (avalancheFuji.id = 43113)
  • type: El tipo de transacción ('legacy' para compatibilidad amplia)

5. Handler Completo de Contrato Inteligente

Handler POST Completo - Interacción con Contrato Inteligente
export async function POST(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const message = searchParams.get('mensaje');

if (!message) {
return NextResponse.json(
{ error: 'Message parameter is required' },
{
status: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
},
);
}

// Calcular timestamp optimizado usando algoritmo personalizado
const optimizedTimestamp = calculateOptimizedTimestamp(message);

// Codificar los datos de la función del contrato
const data = encodeFunctionData({
abi: abi,
functionName: 'storeMessage',
args: [message, BigInt(optimizedTimestamp)],
});

// Crear transacción de contrato inteligente
const tx: TransactionSerializable = {
to: CONTRACT_ADDRESS,
data: data,
chainId: avalancheFuji.id,
type: 'legacy',
};

// Serializar transacción
const serialized = serialize(tx);

// Crear respuesta
const resp: ExecutionResponse = {
serializedTransaction: serialized,
chainId: avalancheFuji.id,
};

return NextResponse.json(resp, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
} catch (error) {
console.error('Error en petición POST:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}

6. Implementar el Algoritmo Personalizado

Algoritmo Personalizado para Timestamp
// Algoritmo personalizado para calcular timestamp optimizado basado en el contenido del mensaje
function calculateOptimizedTimestamp(message: string): number {
const currentTimestamp = Math.floor(Date.now() / 1000);

let offset = 0;

for (let i = 0; i < message.length; i++) {
offset += message.charCodeAt(i) * (i + 1);
}

const maxOffset = 3600;
offset = offset % maxOffset;

return currentTimestamp + offset;
}

Manejo de CORS

Configuración Esencial

Es fundamental configurar CORS antes de probar o desplegar tu mini app. Sin la configuración CORS correcta, tu mini app no funcionará cuando sea accedida desde plataformas externas como el debugger de Sherry o aplicaciones de terceros.

Para permitir que tu mini app sea usada desde diferentes dominios y plataformas, necesitas manejar peticiones preflight de CORS:

Handler CORS OPTIONS
export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 204, // Sin Contenido
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers':
'Content-Type, Authorization, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version',
},
});
}
Por Qué Se Necesita OPTIONS

Cuando las plataformas intentan usar tu mini app desde un dominio diferente, los navegadores primero envían una petición OPTIONS para verificar si se permiten peticiones cross-origin. Este handler le dice al navegador que tu API acepta peticiones de cualquier dominio.

Headers CORS en Todas las Respuestas

Asegúrate de incluir los headers CORS en todas las respuestas de tu API:

Headers CORS Estándar
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}

Probando tu Trigger

Opción 1: App Sherry Social

Pruebas en Producción
  1. Ve a

    https://app.sherry.social/home

  2. En el campo de dirección, ingresa la URL de tu endpoint GET
  3. Ejemplo: http://localhost:3000/api/mi-app (para desarrollo local)

  4. La plataforma renderizará automáticamente tu mini app

Opción 2: Debugger de Sherry (Recomendado para Desarrollo)

Beneficios del Debugger

El debugger está específicamente diseñado para probar y debuggear mini apps durante el desarrollo con múltiples métodos de entrada.

Importante: Despliega Primero en Producción

Para probar tu mini app en el debugger, primero debes desplegarla en un proveedor de hosting como Vercel. El debugger necesita una URL pública accesible para poder cargar tu mini app. Si quieres probar localmente, usa el método JSON copiando la respuesta de tu endpoint GET local y pegándola en el debugger.

  1. Despliega tu mini app en Vercel:
    • vercel --prod
  2. Prueba URL:
    • Pega la URL de tu endpoint GET desplegado
    • Ejemplo: https://mi-sherry-app.vercel.app/api/mi-app
    • Haz clic en "Cargar" para renderizar tu mini app

Proceso de Pruebas Paso a Paso

  1. Desarrollar Localmente

    • Ejecutar npm run dev
    • Probar en localhost:3000/api/mi-app
  2. Desplegar en Vercel

    • vercel --prod
    • Obtener URL pública
  3. Probar en Debugger

    • Usar URL pública del despliegue
    • Verificar renderizado completo
  4. Probar Funcionalidad

    • Completar formulario y probar POST
    • Verificar transacción generada

Guías del Minithon

Qué Hace una Presentación Ganadora del Minithon

Interacción con Contratos Inteligentes

Funcionalidad significativa más allá de transferencias simples

Lógica de Negocio Personalizada

Algoritmos únicos que agregan valor real

Múltiples Parámetros

Manejo sofisticado de datos

Manejo de Errores

Mensajes de error amigables para el usuario

Qué No Será Aceptado en el Minithon

Transferencias Simples de Tokens

Usa nuestro TransferAction en su lugar

Ejemplos Copiados y Pegados

Sin modificaciones significativas

Sin Contratos Inteligentes

Las mini apps deben interactuar con contratos

Implementaciones Rotas

Deben funcionar de extremo a extremo

Resolución de Problemas

Error: "Falló la validación de metadata"

Síntomas: La mini app no carga en el debugger

Soluciones
  • Verifica que todos los campos requeridos estén presentes en la metadata - Confirma que los tipos de datos coincidan con el formato esperado - Usa createMetadata() para validar la estructura - Revisa la consola para errores específicos de validación
Debug de Metadata
try {
const validated = createMetadata(metadata);
console.log('Validación de metadata exitosa:', validated);
} catch (error) {
console.error('Falló la validación de metadata:', error);
}

Error: "La política CORS bloquea la petición"

Síntomas: "El acceso a fetch ha sido bloqueado por la política CORS"

Headers CORS Correctos
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
}

Ejemplo de Código Completo

Aquí tienes un archivo de ruta completo, listo para minithon que demuestra todos los conceptos:

app/api/mi-app/route.ts - Ejemplo Completo
import { NextRequest, NextResponse } from 'next/server';
import { avalancheFuji } from 'viem/chains';
import { createMetadata, Metadata, ValidatedMetadata, ExecutionResponse } from '@sherrylinks/sdk';
import { serialize } from 'wagmi';
import { encodeFunctionData, TransactionSerializable } from 'viem';
import { abi } from '@/blockchain/abi';

const CONTRACT_ADDRESS = '0xTuContratoInteligenteEnFuji';

export async function GET(req: NextRequest) {
try {
const host = req.headers.get('host') || 'localhost:3000';
const protocol = req.headers.get('x-forwarded-proto') || 'http';
const serverUrl = `${protocol}://${host}`;

const metadata: Metadata = {
url: 'https://sherry.social',
title: 'Mensaje con Timestamp',
baseUrl: serverUrl,
description:
'Almacena un mensaje con un timestamp optimizado calculado por nuestro algoritmo',
actions: [
{
type: 'dynamic',
label: 'Almacenar Mensaje',
description:
'Almacena tu mensaje con un timestamp personalizado calculado para almacenamiento óptimo',
chains: { source: 43113 },
path: `/api/mi-app`,
params: [
{
name: 'mensaje',
label: 'Tu Mensaje',
type: 'text',
required: true,
description: 'Ingresa el mensaje que quieres almacenar en la blockchain',
},
],
},
],
};

const validated: ValidatedMetadata = createMetadata(metadata);

return NextResponse.json(validated, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
},
});
} catch (error) {
return NextResponse.json({ error: 'Error al crear metadata' }, { status: 500 });
}
}

export async function POST(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const message = searchParams.get('mensaje');

if (!message) {
return NextResponse.json(
{ error: 'Message parameter is required' },
{
status: 400,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
},
);
}

// Lógica de negocio personalizada
const optimizedTimestamp = calculateOptimizedTimestamp(message);

// Interacción con contrato inteligente
const data = encodeFunctionData({
abi: abi,
functionName: 'storeMessage',
args: [message, BigInt(optimizedTimestamp)],
});

const tx: TransactionSerializable = {
to: CONTRACT_ADDRESS,
data: data,
chainId: avalancheFuji.id,
type: 'legacy',
};

const serialized = serialize(tx);

const resp: ExecutionResponse = {
serializedTransaction: serialized,
chainId: avalancheFuji.id,
};

return NextResponse.json(resp, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
} catch (error) {
console.error('Error en petición POST:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}

export async function OPTIONS(request: NextRequest) {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers':
'Content-Type, Authorization, X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Date, X-Api-Version',
},
});
}

// Algoritmo personalizado - aquí es donde agregas tu valor único
function calculateOptimizedTimestamp(message: string): number {
const currentTimestamp = Math.floor(Date.now() / 1000);

let offset = 0;

for (let i = 0; i < message.length; i++) {
offset += message.charCodeAt(i) * (i + 1);
}

const maxOffset = 3600;
offset = offset % maxOffset;

return currentTimestamp + offset;
}

Repositorio de Ejemplos

Ejemplos Completos Disponibles

Puedes encontrar ejemplos completos de trabajo en nuestro repositorio: SherryLabs/sherry-example

Este repositorio contiene:

  • Tanto ejemplos de transferencia simple como de contrato inteligente
  • Código completo de contrato inteligente con fuente Solidity
  • Scripts de despliegue y configuración
  • Archivos ABI e instrucciones de configuración
  • Ejemplos de múltiples parámetros
  • Utilidades avanzadas de prueba
  • Documentación completa

Próximos Pasos

  1. Comienza con el ejemplo de transferencia simple para entender el flujo
  2. Pasa al ejemplo de contrato inteligente una vez que te sientas cómodo
  3. Experimenta con diferentes tipos de parámetros y validaciones
  4. Prueba minuciosamente usando el debugger