
Part 3: Deploying to Movement Testnet & The Off-Chain Architecture Pivot
We deploy the API Gateway Move smart contract to the Movement Bardock Testnet. This post walks through generating test profiles, executing on-chain RBAC and automated fee splitting via the CLI, and exploring an architectural decision: why high-frequency APIs require transitioning from pure on-chain execution to a zero-trust, off-chain backend utilizing ephemeral session keys.
2) Deploy smart contract to Movement blockchain
2.1) Test profiles (accounts)
To deploy the smart contract to testnet, we need to generate the local profiles and get some free testnet tokens.
To generate a default account (deployer/owner) on testnet:
movement init --profile default --network testnet
(When it asks for a private key, just press Enter to generate a new one. It will automatically fund it from the faucet)
This will output the account address to the console. Take a copy.
We will generate a treasury account to collect the 5% protocol fee:
movement init --profile treasury --network testnet
Generate Test Users:
To properly test our RBAC tiers and rate limits later, let's create three distinct user wallets right now:
movement init --profile user_free --network testnet
movement init --profile user_pro --network testnet
movement init --profile user_premium --network testnet
(Note: Do not hardcode the newly generated default address into the
Move.tomlfile. Leavedeployer = "_"exactly as it is. Hardcoding the production address here will override your[dev-addresses]block and break your local unit tests.)
Locating Your Private Keys
When you run movement init, the CLI automatically stores the generated private keys locally on your machine. You can view all of your generated profiles, addresses, and private keys by opening this file:
cat .movement/config.yaml
Keep note of this file. Later, when we test the React frontend, you will be able to copy these private keys and import them into your Web3 browser wallet. This will allow you to quickly switch between your different test users (user_free, user_pro, etc.) and visually track the token revenue flowing into your treasury profile.
2.2) Publish smart contract to Movement blockchain (2 step process)
Step 1 (Publish): Instead of hardcoding the address, we inject it dynamically into the compiler using the --named-addresses flag.
movement move publish \
--named-addresses deployer=0xYOUR_COPIED_DEFAULT_ADDRESS \
--profile default
This will print out a transaction hash - take note of it.
Step 2 (Initialize the State):
movement move run \
--function-id default::api_gateway::initialize \
--args address:0xYOUR_TREASURY_ADDRESS \
--profile default
This will print out a transaction hash also - take note of this too.
Verify transactions on Movement Explorer
Open Movement Explorer
Insert the transaction above and view the details.
Have a look around the Movement Explorer - look at the history, the code, etc.
3) Testing the Smart Contract via CLI
Before wiring up the Node backend, we need to prove our smart contract logic works entirely on-chain.
3.1) Purchase Subscriptions
Let's upgrade our test users by calling the subscribe function (Arguments: Tier ID, Duration in Months).
Register user_free to Tier 0 (Free):
movement move run \
--function-id default::api_gateway::subscribe \
--args u8:0 u64:1 \
--profile user_free
Upgrade user_pro to Tier 1 (Pro):
movement move run \
--function-id default::api_gateway::subscribe \
--args u8:1 u64:1 \
--profile user_pro
Upgrade user_premium to Tier 2 (Premium):
movement move run \
--function-id default::api_gateway::subscribe \
--args u8:2 u64:1 \
--profile user_premium
3.2) Verify the Automated Fee Split (Monetization)
When user_pro and user_premium paid for their tiers in the previous step, the contract should have automatically routed 5% of their payment to the treasury. Let's check the treasury's bank balance:
movement account balance --profile treasury
Result: You should see the balance has increased from its initial faucet amount. The atomic revenue split works instantly without a secondary payout script.
3.3) Test On-Chain Rate Limits (The Revert Test)
Now we test the log_api_request function which enforces our cooldowns.
Test 1: The Free Tier
Run this command three times in rapid succession (within 60 seconds) for user_free:
movement move run \
--function-id default::api_gateway::log_api_request \
--profile user_free
Result: The first two transactions succeed. The third transaction will FAIL and revert with Execution failed with code 1 (which maps to our E_RATE_LIMIT_EXCEEDED error code in the Move contract).
Test 2: The Pro Tier
Run the exact same command apidly (10+) for user_pro:
movement move run \
--function-id default::api_gateway::log_api_request \
--profile user_pro
Result: The first 10 transactions succeed. The remaining transactions will FAIL and revert with Execution failed with code 1 (which maps to our E_RATE_LIMIT_EXCEEDED error code in the Move contract).
Test 3: The Premium Bypass
Run the exact same command rapidly (20+ times) for user_premium:
movement move run \
--function-id default::api_gateway::log_api_request \
--profile user_premium
Result: We can run this command 100 times in a row. Because Tier 2 bypasses the cooldown logic in our Move code, every transaction succeeds, proving our RBAC logic flawlessly controls access at the blockchain execution level.
4) Design Decision: Why we need an Off-Chain Backend
Now that we've successfully tested the smart contract via the CLI, it might seem logical to just connect a React frontend directly to these Move functions. However, doing so exposes a limitation in pure Web3 architecture.
The Limitation: 100% On-Chain Execution
Our Move contract is currently designed to enforce API rate limits directly on-chain. If we rely strictly on this for a high-traffic web app:
- The UX Problem: Enforcing a "10 requests per second" limit means the user's Web3 wallet would physically pop up and prompt them to sign a transaction 10 times a second.
- The Infrastructure Problem: Blasting the network with thousands of micro-transactions just to read API data would quickly exhaust public RPC limits and unnecessarily bloat the blockchain state.
The Solution: A Zero-Trust Ephemeral Key Gateway
To solve this, we pivot to a zero-trust architecture. We use the blockchain for verifying ownership and delegating session keys, and a Node.js backend for high-speed signature verification and caching.
Here is how the system manages traffic:
- The Write (On-Chain): The user registers their subscription (Free, Pro, or Premium). Then, their browser generates an ephemeral Ed25519 private key in memory. The user's primary wallet authorizes this temporary "session key" on the Movement blockchain for a specific duration.
- The Signature (Gateway): The frontend signs every API request payload (
Method:URL:Timestamp) with the in-memory ephemeral key. The backend intercepts this, mathematically verifies the signature against the Ed25519 curve, and validates the 60-second replay window. - The Cache (Memory): The backend queries the blockchain to ensure the ephemeral key was truly authorized by the user. To prevent RPC limiting, this state is cached locally in an auto-garbage-collected TTL Map for 5 minutes.
Key Benefits of this Architecture
- Unified On-Chain Identity: Every user, including Free tier users, registers their access rights on the blockchain (for a cost of 0 MOVE). This maintains the blockchain as the absolute, single source of truth for all users, eliminating the need for fragmented databases.
- Web2 Speeds, Web3 Security: API endpoints respond in sub-10 milliseconds because they are handled in local memory, but the underlying access rights are still strictly governed by the blockchain.
- Zero PII (Privacy by Design): We do not hold a centralized database of emails, passwords, names, or credit card numbers. The user's wallet signature is their entire identity. A data breach of our servers yields absolutely no personal user data.
- Scalability: By taking the heavy execution load off the Movement network and moving it to local Node.js memory, the gateway can handle massive throughput without hitting RPC bottlenecks.
What's Next?
We've successfully deployed our smart contract and learned why pure on-chain execution isn't viable for high-frequency APIs.
In Part 4, we build the Node.js backend. We will write the custom Express middleware that intercepts API requests, mathematically verifies Ed25519 signatures in real-time, and implements a robust TTL State Cache to resolve blockchain state without burning through public RPC quotas. This basically replaces JWTs with mathematically proven, stateless routing.
Community Discussion
0 Comments
Found this helpful?
If you enjoyed this technical tale, consider supporting my work.