Skip to content

raylsnetwork/rayls-hackathon-starter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

16 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rayls Hackathon Starter

Deploy a bridgeable ERC20 token on your Rayls Privacy Node and transfer it to a public chain.

What You'll Build

Each team gets a dedicated Privacy Node — a sovereign, gasless EVM chain where only you can see what's deployed. Your mission: take an asset from invisible to tradeable, with AI agents governing every step of the reveal.

The five-phase flow:

  1. Design Privately — Deploy rich, complex assets on your Privacy Node. Full metadata, access rules, private balances. Nobody sees anything yet.
  2. AI Attestation — An AI agent inspects your asset and produces a signed proof of existence, posted to the public chain without exposing private details.
  3. Governance & Approval — Submit for institutional disclosure review. An AI compliance agent evaluates the asset and produces a structured recommendation.
  4. Bridge & Simplify — Once approved, your complex private asset crosses to the Public L1 in a simplified, tradeable form. You decide what gets revealed.
  5. List & Trade — Deploy a marketplace on the Public L1. Your once-invisible asset is now publicly discoverable — with full AI-attested provenance.

This starter repo covers the core building block: deploying a bridgeable token on your Privacy Node and teleporting it to the public chain (phases 1 and 4). You'll extend it with AI agents, governance logic, and a marketplace to complete your project.

See Architecture Overview for how the bridge and contracts work under the hood.

Prerequisites

  • Foundry installed (forge, cast)
  • Node.js 18+ installed (for npm)
  • curl and jq for API calls
  • Backend URL + API keys (provided by hackathon organizers)

Quick Setup

git clone <this-repo>
cd rayls-hackathon-starter
forge install
npm install
cp .env.example .env

Edit .env with the values provided by hackathon organizers:

PRIVACY_NODE_RPC_URL=<provided>
DEPLOYMENT_PROXY_REGISTRY=<provided>
PUBLIC_CHAIN_RPC_URL=<provided>
PUBLIC_CHAIN_ID=<provided>
BACKEND_URL=<provided>
USER_AUTH_KEY=<provided>
OPERATOR_AUTH_KEY=<provided>
DEPLOYER_PRIVATE_KEY=<your Foundry wallet key — generate with: cast wallet new>

Then load it:

source .env

Re-run source .env whenever you update the file.


Step-by-Step Guide

Step 1: Register Your User

Create your identity and get wallet addresses for both chains.

curl -X POST "$BACKEND_URL/api/user/onboarding" \
  -H "Authorization: Bearer $USER_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"external_user_id": "your-unique-id"}' | jq

Replace "your-unique-id" with any unique string (e.g. your team name). Use the same ID in Step 2.

Response:

{
  "public_chain_address": "0xAAAA...",
  "private_chain_address": "0xBBBB...",
  "public_chain_private_key": "0x...",
  "private_chain_private_key": "0x...",
  "status": 0,
  "created_at": "..."
}

Save all values from this response — you'll need them in Step 3.

Step 2: Approve Your User

Approve your address pair so you can bridge tokens.

curl -X PATCH "$BACKEND_URL/api/operator/onboarding/status" \
  -H "Authorization: Bearer $OPERATOR_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_user_id": "your-unique-id",
    "public_address": "<public_chain_address>",
    "private_address": "<private_chain_address>",
    "new_status": 1
  }'

A successful response returns HTTP 200 with an empty body.

Step 3: Update Your Environment

Update .env with the values from Step 1's response, then reload:

REGISTERED_PRIVATE_KEY=<private_chain_private_key from response>
MINT_RECIPIENT=<private_chain_address from response>
TRANSFER_TO=<public_chain_address from response>
TOKEN_NAME="My Token"
TOKEN_SYMBOL="MTK"
source .env

Step 4: Deploy Your Token

Before deploying: Make sure DEPLOYER_PRIVATE_KEY is set in your .env. Generate one with cast wallet new and copy the private key. Also make sure TOKEN_NAME and TOKEN_SYMBOL are unique — if a token with the same symbol was already deployed, the transaction will revert.

forge script script/Deploy.s.sol --rpc-url $PRIVACY_NODE_RPC_URL --broadcast --legacy

The script discovers infrastructure addresses from the on-chain registry and deploys your token. Copy the deployed address and set it in your .env:

TOKEN_ADDRESS=<deployed address from output>

Then reload:

source .env

Step 5: Register Your Token

Register the deployed token with the governance system.

curl -X POST "$BACKEND_URL/api/user/tokens" \
  -H "Authorization: Bearer $USER_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"$TOKEN_NAME\",
    \"symbol\": \"$TOKEN_SYMBOL\",
    \"address\": \"$TOKEN_ADDRESS\",
    \"uri\": \"\",
    \"standard\": 1
  }" | jq

standard: 1 = ERC20, 2 = ERC721, 3 = ERC1155

Step 6: Approve Your Token

Activate the token. This triggers the relayer to deploy a mirror contract on the public chain.

curl -X PATCH "$BACKEND_URL/api/operator/tokens/status" \
  -H "Authorization: Bearer $OPERATOR_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"address\": \"$TOKEN_ADDRESS\", \"status\": 1}"

A successful response returns HTTP 200 with an empty body.

Step 7: Wait for Mirror Deployment

After approval, the relayer automatically:

  1. Deploys a PublicChainERC20 mirror on the public chain
  2. Maps the private → public token addresses
  3. Authorizes the mirror as a sender

This takes ~30-60 seconds. You can verify by running CheckBalance — if it prints a mirror address instead of reverting, the mirror is deployed:

forge script script/CheckBalance.s.sol --rpc-url $PRIVACY_NODE_RPC_URL

Step 8: Mint Tokens

Mint tokens to your registered private-chain address (the one from Step 1).

forge script script/Mint.s.sol --rpc-url $PRIVACY_NODE_RPC_URL --broadcast --legacy

This calls mint() (onlyOwner) on your token, sending tokens to MINT_RECIPIENT.

Step 9: Transfer to Public Chain

Bridge tokens from the Privacy Node to the public chain.

forge script script/Transfer.s.sol --rpc-url $PRIVACY_NODE_RPC_URL --broadcast --legacy

This calls teleportToPublicChain() which:

  1. Locks tokens on the Privacy Node
  2. Dispatches a cross-chain message
  3. The relayer picks it up and submits to the public chain
  4. The mirror contract mints tokens for you on the public chain

Note: This script signs with REGISTERED_PRIVATE_KEY (your registered private-chain address), not DEPLOYER_PRIVATE_KEY. The onlyRegisteredUsers modifier requires msg.sender to be an approved user.

Step 10: Verify on Public Chain

Check your balance on the public chain mirror contract. Your tokens should appear within a minute after the transfer.

forge script script/CheckBalance.s.sol --rpc-url $PRIVACY_NODE_RPC_URL

This script automatically discovers the mirror contract address from the on-chain governance registry, then queries the public chain for your balance.


Using ERC721 or ERC1155

The same flow works for NFTs and multi-tokens. Use the matching scripts and set standard accordingly when registering via API.

Standard Contract Deploy Script Mint Script Transfer Script API standard
ERC20 HackathonToken.sol Deploy.s.sol Mint.s.sol Transfer.s.sol 1
ERC721 HackathonNFT.sol DeployNFT.s.sol MintNFT.s.sol TransferNFT.s.sol 2
ERC1155 HackathonMultiToken.sol DeployMultiToken.s.sol MintMultiToken.s.sol TransferMultiToken.s.sol 3

Configure the matching env vars (see .env.example for NFT_* and MULTI_TOKEN_* sections), then follow Steps 4-10 using the corresponding scripts.


Understanding the Two Keys

Key Used By Purpose
DEPLOYER_PRIVATE_KEY Deploy.s.sol, Mint.s.sol Your Foundry wallet. Owns the token contract. Can mint/burn.
REGISTERED_PRIVATE_KEY Transfer.s.sol Your registered private-chain address (from onboarding). Required by onlyRegisteredUsers modifier.

Challenge Tracks

Choose one of the three challenges:

RWA Tokenization

Tokenize a real-world asset (bond, invoice, fund share) privately on your Privacy Node. An AI compliance agent reviews it before it goes public. Bridge a clean receipt token to a marketplace.

  • AI Role: Governance compliance reviewer
  • Key contracts: Your token + a governance contract that accepts/rejects AI recommendations

Confidential NFT Reveal

Mint a high-value digital asset with fully private metadata. An AI oracle attests its existence on-chain. The asset reveals itself to buyers only after purchase.

  • AI Role: Cross-chain attestation oracle
  • Key contracts: Your token + an attestation contract on the public chain

Autonomous Institution Agent

Build an AI agent that runs an entire institutional treasury: detecting assets, attesting them, submitting governance, bridging, and listing — autonomously. Humans watch; the agent operates.

  • AI Role: Full autonomous orchestration
  • Key contracts: Your token + the full five-phase pipeline scripted end-to-end

AI Agent Skeleton

The agent/ directory contains a minimal TypeScript example showing how to connect an AI to on-chain interactions. It demonstrates one pattern in three steps:

  1. Read — connects to the public chain via ethers.js and reads a bridged token's name, symbol, and supply
  2. Analyze — sends the token data to an LLM and gets back a structured verdict (approved/rejected, score, reasoning)
  3. Write — posts the AI's verdict as an on-chain attestation by calling Attestation.sol
cd agent && npm install && cp .env.example .env
# Fill in your values, then:
npm start

Supports Google Gemini (free tier — no credit card), Anthropic (Claude), OpenAI, and OpenRouter. Set AI_PROVIDER in agent/.env.

OpenRouter gives access to 300+ models through a single API key — including several free models ideal for hackathons:

Model OPENROUTER_MODEL value
Meta Llama 3.3 70B (free) meta-llama/llama-3.3-70b-instruct:free
DeepSeek R1 (free) deepseek/deepseek-r1:free
Mistral 7B (free) mistralai/mistral-7b-instruct:free
Gemma 3 27B (free) google/gemma-3-27b-it:free

Get your free API key at openrouter.ai/keys, then set in agent/.env:

AI_PROVIDER=openrouter
OPENROUTER_API_KEY=sk-or-...
OPENROUTER_MODEL=meta-llama/llama-3.3-70b-instruct:free

This is one pattern — adapt the read/analyze/write steps to your challenge track. The repo also includes Attestation.sol and DeployPublic.s.sol as optional building blocks.

Marketplace

The repo includes Marketplace.sol — an escrow contract you deploy on the public chain to sell your bridged tokens. You list tokens, set prices, and anyone can buy with USDR.

Prerequisites

Your public chain address needs gas (USDR) on the public chain to deploy the marketplace and create listings. The Privacy Node is gasless, but the public chain is not. Ask organizers for a funded wallet or use a faucet.

You also need the public chain private key from your onboarding response (Step 1). Save it — you'll need it for all public chain interactions.

Deploy

DEPLOYER_PRIVATE_KEY=<your public_chain_private_key> \
  forge script script/DeployMarketplace.s.sol --rpc-url $PUBLIC_CHAIN_RPC_URL --broadcast --legacy

Note: this uses your public chain key (not the Privacy Node deployer key), because the marketplace lives on the public chain and you need gas there.

Save the address:

MARKETPLACE_ADDRESS=<from output>

List Tokens for Sale

After bridging tokens to the public chain (Steps 1-10), you can list them on your marketplace.

1. Approve the marketplace to spend your tokens:

cast send <PUBLIC_MIRROR_ADDRESS> \
  "approve(address,uint256)" \
  <MARKETPLACE_ADDRESS> <AMOUNT> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <PUBLIC_CHAIN_PRIVATE_KEY> \
  --legacy

2. List — tokens are transferred into the marketplace escrow:

# ERC20: assetType=0, tokenId=0
cast send <MARKETPLACE_ADDRESS> \
  "list(address,uint8,uint256,uint256,uint256)" \
  <PUBLIC_MIRROR_ADDRESS> 0 0 <AMOUNT> <PRICE> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <PUBLIC_CHAIN_PRIVATE_KEY> \
  --legacy
# ERC721: assetType=1, amount is ignored
cast send <MARKETPLACE_ADDRESS> \
  "list(address,uint8,uint256,uint256,uint256)" \
  <PUBLIC_MIRROR_ADDRESS> 1 <TOKEN_ID> 1 <PRICE> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <PUBLIC_CHAIN_PRIVATE_KEY> \
  --legacy

Buy

Anyone can buy a listed asset by sending USDR:

cast send <MARKETPLACE_ADDRESS> \
  "buy(uint256)" <LISTING_ID> \
  --value <PRICE> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <BUYER_PRIVATE_KEY> \
  --legacy

Manage Listings

# Update price
cast send <MARKETPLACE_ADDRESS> "update(uint256,uint256)" <LISTING_ID> <NEW_PRICE_WEI> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL --private-key <PUBLIC_CHAIN_PRIVATE_KEY> --legacy

# Delist (returns tokens to you)
cast send <MARKETPLACE_ADDRESS> "delist(uint256)" <LISTING_ID> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL --private-key <PUBLIC_CHAIN_PRIVATE_KEY> --legacy

# View a listing
cast call <MARKETPLACE_ADDRESS> "getListing(uint256)" <LISTING_ID> --rpc-url $PUBLIC_CHAIN_RPC_URL

# View all active listings
cast call <MARKETPLACE_ADDRESS> "getActiveListings()" --rpc-url $PUBLIC_CHAIN_RPC_URL

Customizing Your Token

Edit src/HackathonToken.sol — there are commented-out examples for:

  1. Custom decimals — e.g. 6 decimals for stablecoins
  2. Receive validation — add custom checks on incoming public chain transfers
  3. AccessControl — role-based mint/burn permissions

Transferring Back (Public → Private)

To return tokens from the public chain to your Privacy Node, call teleportToPrivacyNode() on the mirror contract. You'll need the public chain RPC and your public chain private key (from the onboarding response).

ERC20:

cast send <PUBLIC_MIRROR_ADDRESS> \
  "teleportToPrivacyNode(address,uint256,uint256)" \
  <YOUR_PRIVATE_CHAIN_ADDRESS> <AMOUNT> <PRIVACY_NODE_CHAIN_ID> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <PUBLIC_CHAIN_PRIVATE_KEY> \
  --legacy

ERC721:

cast send <PUBLIC_MIRROR_ADDRESS> \
  "teleportToPrivacyNode(address,uint256,uint256)" \
  <YOUR_PRIVATE_CHAIN_ADDRESS> <TOKEN_ID> <PRIVACY_NODE_CHAIN_ID> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <PUBLIC_CHAIN_PRIVATE_KEY> \
  --legacy

ERC1155:

cast send <PUBLIC_MIRROR_ADDRESS> \
  "teleportToPrivacyNode(address,uint256,uint256,uint256)" \
  <YOUR_PRIVATE_CHAIN_ADDRESS> <TOKEN_ID> <AMOUNT> <PRIVACY_NODE_CHAIN_ID> \
  --rpc-url $PUBLIC_CHAIN_RPC_URL \
  --private-key <PUBLIC_CHAIN_PRIVATE_KEY> \
  --legacy

The mirror address is shown in the CheckBalance.s.sol output, or query it via the governance API.

Troubleshooting

Error Cause Fix
User not registered Your address isn't approved in RNUserGovernanceV1 Complete Steps 1-2
Token not found or reverts on transfer Token not registered/activated Complete Steps 5-6 and wait for mirror deployment
Ownable: caller is not the owner Wrong key used for mint Use DEPLOYER_PRIVATE_KEY for Mint.s.sol
Transfer succeeds but no tokens on public chain Relayer hasn't processed yet Wait ~60 seconds, then check again
Failed to get EIP-1559 fees Rayls Privacy Nodes are gasless and don't support EIP-1559 Add --legacy flag to your forge script command (already included in all commands above)
Token approval returns HTTP 500 Backend needs a moment after token registration Retry the same curl command after 5 seconds
Mirror address returns 0x0000...0000 Relayer hasn't deployed the mirror yet Wait 30-60 seconds after token approval
Token register says "not a valid deployed contract" TOKEN_ADDRESS not updated in .env after deploy Set TOKEN_ADDRESS=<address from Step 4 output> and run source .env
Redeploy reverts with "execution reverted" Token with the same symbol already registered Change TOKEN_SYMBOL in .env to a unique value and redeploy

Debugging

Inspect a failed transaction:

cast run <TX_HASH> --rpc-url $PRIVACY_NODE_RPC_URL

Check if the mirror contract is deployed:

# Get the TokenGovernance address
TOKEN_GOV=$(cast call $DEPLOYMENT_PROXY_REGISTRY "getContract(string)(address)" "RNTokenGovernance" --rpc-url $PRIVACY_NODE_RPC_URL)

# Query the public mirror address
cast call $TOKEN_GOV "getPublicAddressByPrivateAddress(address)(address)" $TOKEN_ADDRESS --rpc-url $PRIVACY_NODE_RPC_URL

Check your balances:

# Balance on Privacy Node
cast call $TOKEN_ADDRESS "balanceOf(address)(uint256)" <YOUR_ADDRESS> --rpc-url $PRIVACY_NODE_RPC_URL

# Locked amount (tokens currently bridged to public)
cast call $TOKEN_ADDRESS "getLockedAmount(address)(uint256)" <YOUR_ADDRESS> --rpc-url $PRIVACY_NODE_RPC_URL

Further Reading

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors