Skip to content

Commit 247c45e

Browse files
committed
Add Polygon & Gasless USDC tutorial sections
- Section 5: Polygon basics (intro, wallet, POL, USDC) - Section 6: Polygon & USDC (wallet, mainnet transfers) - Section 7: Gasless transfers (OpenGSN v2.2.6) - Wallet setup, intro, gasful baseline - Static relay, relay discovery, optimized fees - Following Nimiq wallet implementation patterns
1 parent 5624d13 commit 247c45e

File tree

93 files changed

+4593
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+4593
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ nimiq-tutorial/
123123
│ │ ├── 1-connecting-to-network/
124124
│ │ ├── 2-working-with-transactions/
125125
│ │ ├── 3-staking-and-validators/
126-
│ │ └── 4-miscellaneous/
126+
│ │ └── 4-polygon/
127127
│ └── templates/ # Code templates
128128
├── public/ # Static assets
129129
├── scripts/ # Build scripts
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { ethers } from 'ethers'
2+
import { createWalletFromPassword } from './lib/wallet.js'
3+
4+
const RPC_URL = 'https://rpc-amoy.polygon.technology'
5+
const USDC_ADDRESS = '0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582'
6+
const USDC_ABI = [
7+
'function transfer(address to, uint256 amount) returns (bool)',
8+
'function balanceOf(address account) view returns (uint256)',
9+
'function decimals() view returns (uint8)'
10+
]
11+
12+
// 🔐 WALLET SETUP: Change this to your own unique password!
13+
// Same password = same wallet address every time (great for tutorials)
14+
const WALLET_PASSWORD = 'change_me_to_something_unique_like_pizza_unicorn_2024'
15+
16+
// 📤 RECIPIENT: This is a Nimiq-controlled account that collects tutorial demo transfers
17+
// If you accidentally send large amounts or want your funds back, contact us and we'll return them!
18+
//
19+
// 💡 Want to send to yourself instead? Uncomment these lines to create a second wallet:
20+
// const recipientWallet = createWalletFromPassword('my_second_wallet_password_banana_2024')
21+
// const RECIPIENT = recipientWallet.address
22+
// This way you control both wallets and can recover any funds sent!
23+
//
24+
// Or create your own wallet with a standard 24-word mnemonic (see lib/wallet.js)
25+
const RECIPIENT = '0xA3E49ef624bEaC43D29Af86bBFdE975Abaa0E184'
26+
27+
async function main() {
28+
console.log('🚀 Polygon Basics - Complete Demo\n')
29+
console.log('This demo shows all concepts from lessons 2-4:\n')
30+
31+
// ========== LESSON 2: WALLET SETUP & FAUCETS ==========
32+
console.log('📚 LESSON 2: Wallet Setup & Faucets')
33+
console.log('─'.repeat(50))
34+
35+
const provider = new ethers.providers.JsonRpcProvider(RPC_URL)
36+
37+
// Create wallet from password (see lib/wallet.js for alternatives)
38+
const wallet = createWalletFromPassword(WALLET_PASSWORD).connect(provider)
39+
console.log('✅ Wallet created from password')
40+
41+
if (WALLET_PASSWORD === 'change_me_to_something_unique_like_pizza_unicorn_2024') {
42+
console.log('⚠️ Using default password! Change WALLET_PASSWORD to your own unique string.')
43+
}
44+
45+
console.log('📍 Address:', wallet.address)
46+
console.log('🔗 View on explorer:', `https://amoy.polygonscan.com/address/${wallet.address}`)
47+
48+
// Check balances
49+
const polBalance = await provider.getBalance(wallet.address)
50+
console.log('💰 POL Balance:', ethers.utils.formatEther(polBalance), 'POL')
51+
52+
const usdc = new ethers.Contract(USDC_ADDRESS, USDC_ABI, wallet)
53+
const usdcBalance = await usdc.balanceOf(wallet.address)
54+
const decimals = await usdc.decimals()
55+
console.log('💵 USDC Balance:', ethers.utils.formatUnits(usdcBalance, decimals), 'USDC')
56+
57+
// Convert to 24-word mnemonic so you can import into any wallet
58+
const mnemonic = ethers.Wallet.fromMnemonic(
59+
ethers.utils.entropyToMnemonic(ethers.utils.hexZeroPad(wallet.privateKey, 32))
60+
).mnemonic.phrase
61+
console.log('\n📝 Mnemonic (24 words):', mnemonic)
62+
console.log('💡 You can import this mnemonic into any wallet to check your balances:')
63+
console.log(' • Nimiq Testnet Wallet: https://wallet.nimiq-testnet.com/ (supports Amoy USDC)')
64+
console.log(' • Note: Nimiq Wallet does not support POL, only USDC/USDT')
65+
console.log(' • Or use MetaMask, Trust Wallet, etc.\n')
66+
67+
if (polBalance.eq(0)) {
68+
console.log('\n⚠️ No POL found! Get free tokens from:')
69+
console.log(' POL & USDC: https://faucet.polygon.technology/')
70+
console.log(' USDC also: https://faucet.circle.com/')
71+
console.log(' Then run this demo again!\n')
72+
return
73+
}
74+
75+
// ========== LESSON 3: SENDING POL ==========
76+
console.log('\n📚 LESSON 3: Sending POL Transactions')
77+
console.log('─'.repeat(50))
78+
79+
const POL_AMOUNT = '0.0001' // Minimal amount for demo
80+
81+
console.log('📤 Sending', POL_AMOUNT, 'POL to', RECIPIENT)
82+
83+
try {
84+
// Get current gas price and ensure minimum for Polygon
85+
const feeData = await provider.getFeeData()
86+
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas.lt(ethers.utils.parseUnits('30', 'gwei'))
87+
? ethers.utils.parseUnits('30', 'gwei')
88+
: feeData.maxPriorityFeePerGas
89+
90+
// Ensure maxFeePerGas is higher than maxPriorityFeePerGas
91+
const maxFeePerGas = feeData.maxFeePerGas.lt(maxPriorityFeePerGas)
92+
? maxPriorityFeePerGas.mul(2)
93+
: feeData.maxFeePerGas
94+
95+
const tx = await wallet.sendTransaction({
96+
to: RECIPIENT,
97+
value: ethers.utils.parseEther(POL_AMOUNT),
98+
maxPriorityFeePerGas,
99+
maxFeePerGas
100+
})
101+
console.log('⏳ Transaction hash:', tx.hash)
102+
103+
const receipt = await tx.wait()
104+
console.log('✅ Confirmed in block:', receipt.blockNumber)
105+
console.log('⛽ Gas used:', receipt.gasUsed.toString())
106+
console.log('🔗 View:', `https://amoy.polygonscan.com/tx/${tx.hash}`)
107+
108+
// Show balance change
109+
const newPolBalance = await provider.getBalance(wallet.address)
110+
const spent = polBalance.sub(newPolBalance)
111+
console.log('📊 Total spent:', ethers.utils.formatEther(spent), 'POL (including gas)')
112+
} catch (error) {
113+
console.log('❌ Failed:', error.message)
114+
}
115+
116+
// ========== LESSON 4: ERC20 & USDC ==========
117+
console.log('\n📚 LESSON 4: ERC20 Tokens & USDC Transfers')
118+
console.log('─'.repeat(50))
119+
120+
if (usdcBalance.eq(0)) {
121+
console.log('⚠️ No USDC found! Get free USDC from:')
122+
console.log(' https://faucet.polygon.technology/')
123+
console.log(' https://faucet.circle.com/')
124+
console.log(' Then try this section again!\n')
125+
return
126+
}
127+
128+
const USDC_AMOUNT = '0.01' // Minimal amount for demo
129+
console.log('📤 Sending', USDC_AMOUNT, 'USDC to', RECIPIENT)
130+
131+
try {
132+
// Get current gas price and ensure minimum for Polygon
133+
const feeData = await provider.getFeeData()
134+
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas.lt(ethers.utils.parseUnits('30', 'gwei'))
135+
? ethers.utils.parseUnits('30', 'gwei')
136+
: feeData.maxPriorityFeePerGas
137+
138+
// Ensure maxFeePerGas is higher than maxPriorityFeePerGas
139+
const maxFeePerGas = feeData.maxFeePerGas.lt(maxPriorityFeePerGas)
140+
? maxPriorityFeePerGas.mul(2)
141+
: feeData.maxFeePerGas
142+
143+
const amountInBaseUnits = ethers.utils.parseUnits(USDC_AMOUNT, decimals)
144+
const tx = await usdc.transfer(RECIPIENT, amountInBaseUnits, {
145+
maxPriorityFeePerGas,
146+
maxFeePerGas
147+
})
148+
console.log('⏳ Transaction hash:', tx.hash)
149+
150+
const receipt = await tx.wait()
151+
console.log('✅ Confirmed in block:', receipt.blockNumber)
152+
console.log('🔗 View:', `https://amoy.polygonscan.com/tx/${tx.hash}`)
153+
154+
// Show balance change
155+
const newUsdcBalance = await usdc.balanceOf(wallet.address)
156+
console.log('📊 New USDC balance:', ethers.utils.formatUnits(newUsdcBalance, decimals), 'USDC')
157+
158+
const recipientBalance = await usdc.balanceOf(RECIPIENT)
159+
console.log('📊 Recipient USDC:', ethers.utils.formatUnits(recipientBalance, decimals), 'USDC')
160+
161+
// Show POL used for gas (still needed for ERC20!)
162+
const finalPolBalance = await provider.getBalance(wallet.address)
163+
console.log('⛽ POL balance:', ethers.utils.formatEther(finalPolBalance), 'POL (gas paid in POL!)')
164+
} catch (error) {
165+
console.log('❌ Failed:', error.message)
166+
}
167+
168+
console.log('\n🎉 Demo complete! Now try lessons 2-4 step by step.')
169+
}
170+
171+
main().catch(console.error)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { ethers } from 'ethers'
2+
3+
/**
4+
* Creates a wallet from a password string.
5+
*
6+
* WHY USE THIS?
7+
* - You can recreate the same wallet anytime by using the same password
8+
* - No need to save private keys in files or worry about losing them
9+
* - Just remember your password and you can access your wallet from any code
10+
*
11+
* HOW IT WORKS:
12+
* - Your password is hashed (keccak256) to create a deterministic private key
13+
* - Same password = same private key = same wallet address every time
14+
*
15+
* SECURITY NOTE:
16+
* - This is PERFECT for testnets (learning, experiments)
17+
* - For mainnet with real money, use a hardware wallet or proper mnemonic phrase
18+
*/
19+
export function createWalletFromPassword(password) {
20+
// Hash the password to get a deterministic private key
21+
const privateKey = ethers.utils.id(password)
22+
const wallet = new ethers.Wallet(privateKey)
23+
24+
return wallet
25+
}
26+
27+
/**
28+
* Alternative wallet creation methods:
29+
*
30+
* 1. FROM MNEMONIC (12/24 word phrase):
31+
* const mnemonic = "word1 word2 word3 ... word12"
32+
* const wallet = ethers.Wallet.fromMnemonic(mnemonic)
33+
*
34+
* 2. FROM PRIVATE KEY (hex string):
35+
* const privateKey = "0x1234567890abcdef..."
36+
* const wallet = new ethers.Wallet(privateKey)
37+
*
38+
* 3. RANDOM WALLET (new every time):
39+
* const wallet = ethers.Wallet.createRandom()
40+
* console.log('Save this mnemonic:', wallet.mnemonic.phrase)
41+
*
42+
* 4. FROM PASSWORD (this method - best for tutorials):
43+
* const wallet = createWalletFromPassword("my_unique_password_123")
44+
*/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"polygon-basics-demo","version":"1.0.0","type":"module","scripts":{"demo":"node --watch index.js"},"dependencies":{"ethers":"^5.7.2"}}

0 commit comments

Comments
 (0)