什麼是贖回?
當市場結算後,Oracle 會報告最終結果。持有獲勝結果代幣的用戶可以將這些代幣兌換為 USDC:Copy
如果 Yes 獲勝:
100 Yes 代幣 → 100 USDC
100 No 代幣 → 0 USDC (無價值)
如果 No 獲勝:
100 Yes 代幣 → 0 USDC (無價值)
100 No 代幣 → 100 USDC
獲勝回報:每個獲勝代幣可以 1:1 兌換為 USDC。如果你以 0.65 的價格買入 Yes 代幣,Yes 獲勝後,每個代幣價值 1 USDC,利潤為 0.35 USDC。
贖回流程
市場結算流程
- 事件發生:現實世界的事件結果確定
- Oracle 報告:UMA Oracle 提交結果
- 爭議期:短暫的爭議期(通常幾小時)
- 結算確認:結果最終確定
- 贖回開放:用戶可以贖回獲勝代幣
方法 1:通過 Polymarket 界面
最簡單的方法:- 訪問已結算的市場頁面
- 如果你持有獲勝代幣,會看到”Redeem”按鈕
- 點擊贖回
- 確認交易
- USDC 會自動轉入你的錢包
Polymarket 會自動顯示可贖回的金額,並處理所有技術細節。
方法 2:直接調用智能合約
適合自動化和批量贖回。Python 示例
Copy
from web3 import Web3
# 連接到 Polygon
w3 = Web3(Web3.HTTPProvider('https://polygon-rpc.com'))
# CTF 合約
ctf_address = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045'
ctf_abi = [...] # CTF ABI
ctf_contract = w3.eth.contract(address=ctf_address, abi=ctf_abi)
# USDC 地址
usdc_address = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174'
# 市場信息
condition_id = '0x...' # 已結算市場的條件 ID
parent_collection_id = '0x' + '0' * 64
# 確定獲勝結果的索引集
# 如果 Yes 獲勝,使用 [1]
# 如果 No 獲勝,使用 [2]
winning_index_sets = [1] # Yes 獲勝
# 贖回獲勝代幣
redeem_tx = ctf_contract.functions.redeemPositions(
usdc_address, # 抵押品代幣 (USDC)
parent_collection_id, # 父集合 ID
condition_id, # 條件 ID
winning_index_sets # 獲勝結果的索引集
).build_transaction({
'from': your_address,
'nonce': w3.eth.get_transaction_count(your_address),
'gas': 150000,
'gasPrice': w3.eth.gas_price
})
# 籤署並發送交易
signed_tx = w3.eth.account.sign_transaction(redeem_tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"贖回成功! 交易哈希: {tx_hash.hex()}")
print(f"Gas 使用: {receipt['gasUsed']}")
TypeScript 示例
Copy
import { ethers } from 'ethers';
// 連接到 Polygon
const provider = new ethers.providers.JsonRpcProvider('https://polygon-rpc.com');
const wallet = new ethers.Wallet(privateKey, provider);
// CTF 合約
const ctfAddress = '0x4D97DCd97eC945f40cF65F87097ACe5EA0476045';
const ctfAbi = [...]; // CTF ABI
const ctfContract = new ethers.Contract(ctfAddress, ctfAbi, wallet);
// USDC 地址
const usdcAddress = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174';
async function redeemPositions() {
const conditionId = '0x...'; // 已結算市場的條件 ID
const parentCollectionId = '0x' + '0'.repeat(64);
// 獲勝結果 (1 = Yes, 2 = No)
const winningIndexSets = [1]; // Yes 獲勝
try {
// 贖回代幣
const redeemTx = await ctfContract.redeemPositions(
usdcAddress,
parentCollectionId,
conditionId,
winningIndexSets
);
console.log('贖回交易已發送:', redeemTx.hash);
const receipt = await redeemTx.wait();
console.log('贖回成功!');
console.log('Gas 使用:', receipt.gasUsed.toString());
// 檢查收到的 USDC
const usdcContract = new ethers.Contract(usdcAddress, usdcAbi, wallet);
const balance = await usdcContract.balanceOf(wallet.address);
console.log('USDC 餘額:', ethers.utils.formatUnits(balance, 6));
} catch (error) {
console.error('贖回失敗:', error);
}
}
redeemPositions();
檢查市場結算狀態
在贖回前,確認市場已結算:Copy
def check_market_settled(condition_id):
"""
檢查市場是否已結算
"""
# 查詢支付向量
payout_numerators = ctf_contract.functions.payoutNumerators(
condition_id,
0 # Yes 的索引
).call()
if payout_numerators == 0:
# 未結算
print("市場尚未結算")
return False, None
else:
# 已結算,確定獲勝方
yes_payout = ctf_contract.functions.payoutNumerators(
condition_id, 0
).call()
no_payout = ctf_contract.functions.payoutNumerators(
condition_id, 1
).call()
if yes_payout > no_payout:
print("市場已結算: Yes 獲勝")
return True, 'YES'
elif no_payout > yes_payout:
print("市場已結算: No 獲勝")
return True, 'NO'
else:
print("市場已結算: 無效/平局")
return True, 'INVALID'
# 使用示例
settled, winner = check_market_settled(condition_id)
計算可贖回金額
檢查你可以贖回多少 USDC:Copy
def calculate_redeemable_amount(address, position_id, condition_id):
"""
計算可贖回的 USDC 數量
"""
# 檢查市場結算狀態
settled, winner = check_market_settled(condition_id)
if not settled:
print("市場尚未結算,無法贖回")
return 0
# 查詢代幣餘額
balance = ctf_contract.functions.balanceOf(
address,
int(position_id, 16)
).call()
# 查詢該位置的支付比例
# 獲勝代幣的支付比例是 1,失敗代幣是 0
payout = ctf_contract.functions.payoutNumerators(
condition_id,
0 if winner == 'YES' else 1
).call()
payout_denominator = ctf_contract.functions.payoutDenominator(
condition_id
).call()
# 計算可贖回金額
redeemable = balance * payout / payout_denominator
print(f"代幣餘額: {balance / 10**6}")
print(f"支付比例: {payout}/{payout_denominator}")
print(f"可贖回: {redeemable / 10**6} USDC")
return redeemable
# 使用示例
yes_position_id = '0x...'
redeemable = calculate_redeemable_amount(
your_address,
yes_position_id,
condition_id
)
批量贖回多個市場
如果你在多個已結算市場都有獲勝代幣:Copy
def batch_redeem(markets_info):
"""
批量贖回多個市場的代幣
Args:
markets_info: 列表,每個元素包含 {condition_id, index_sets}
"""
total_redeemed = 0
for market in markets_info:
condition_id = market['condition_id']
index_sets = market['index_sets'] # [1] for Yes, [2] for No
try:
# 檢查是否已結算
settled, winner = check_market_settled(condition_id)
if not settled:
print(f"市場 {condition_id[:8]}... 尚未結算,跳過")
continue
# 贖回
redeem_tx = ctf_contract.functions.redeemPositions(
usdc_address,
'0x' + '0' * 64,
condition_id,
index_sets
).build_transaction({
'from': your_address,
'nonce': w3.eth.get_transaction_count(your_address),
})
signed_tx = w3.eth.account.sign_transaction(redeem_tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)
print(f"市場 {condition_id[:8]}... 贖回交易: {tx_hash.hex()}")
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f" ✓ 已確認,Gas: {receipt['gasUsed']}")
except Exception as e:
print(f"市場 {condition_id[:8]}... 贖回失敗: {e}")
continue
# 使用示例
markets = [
{'condition_id': '0xabc...', 'index_sets': [1]}, # Yes 獲勝
{'condition_id': '0xdef...', 'index_sets': [2]}, # No 獲勝
{'condition_id': '0x123...', 'index_sets': [1]}, # Yes 獲勝
]
batch_redeem(markets)
自動監控和贖回
創建一個自動化腳本,監控市場結算並自動贖回:Copy
import time
from datetime import datetime
def auto_redeem_monitor(condition_ids, check_interval=300):
"""
監控市場結算並自動贖回
Args:
condition_ids: 要監控的條件 ID 列表
check_interval: 檢查間隔(秒)
"""
redeemed = set() # 已贖回的市場
while condition_ids:
print(f"\n[{datetime.now()}] 檢查市場狀態...")
for condition_id in condition_ids[:]:
if condition_id in redeemed:
continue
try:
# 檢查是否已結算
settled, winner = check_market_settled(condition_id)
if settled:
print(f"市場 {condition_id[:8]}... 已結算: {winner}")
# 確定要贖回的索引
if winner == 'YES':
index_sets = [1]
elif winner == 'NO':
index_sets = [2]
else:
# 無效結果,可能需要贖回兩邊
index_sets = [1, 2]
# 執行贖回
redeem_tx = ctf_contract.functions.redeemPositions(
usdc_address,
'0x' + '0' * 64,
condition_id,
index_sets
).build_transaction({
'from': your_address,
'nonce': w3.eth.get_transaction_count(your_address),
})
signed_tx = w3.eth.account.sign_transaction(
redeem_tx, private_key
)
tx_hash = w3.eth.send_raw_transaction(
signed_tx.rawTransaction
)
print(f" → 贖回交易: {tx_hash.hex()}")
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f" ✓ 贖回成功")
# 標記為已贖回
redeemed.add(condition_id)
condition_ids.remove(condition_id)
else:
print(f"市場 {condition_id[:8]}... 尚未結算")
except Exception as e:
print(f"市場 {condition_id[:8]}... 錯誤: {e}")
if condition_ids:
print(f"\n等待 {check_interval} 秒後再次檢查...")
time.sleep(check_interval)
else:
print("\n所有市場已贖回完成!")
break
# 使用示例
markets_to_monitor = [
'0xabc123...',
'0xdef456...',
'0x789ghi...',
]
auto_redeem_monitor(markets_to_monitor, check_interval=300) # 每5分鐘檢查
Gas 成本
贖回操作的典型 Gas 成本(Polygon):| 操作 | Gas 使用量 | 成本 @ 100 Gwei |
|---|---|---|
| 單次贖回 | ~110,000 | $0.011 |
| 批量贖回 (3個) | ~300,000 | $0.030 |
特殊情況
無效結果
某些情況下,市場可能被標記為無效:- 問題模糊不清
- 結果無法驗證
- Oracle 無法確定結果
Copy
# 無效結果通常意味著 Yes 和 No 各 50%
# 你可能需要贖回兩邊
index_sets = [1, 2] # 贖回 Yes 和 No
# 每個代幣可以贖回 0.5 USDC
# 如果持有 100 Yes,可以贖回 50 USDC
部分贖回
你可以選擇只贖回部分代幣(雖然不常見):Copy
# 這需要更複雜的合約交互
# 通常直接贖回全部更簡單
贖回後驗證
驗證贖回是否成功:Copy
def verify_redemption(address, condition_id):
"""
驗證贖回後的狀態
"""
# 檢查 USDC 餘額
usdc_balance = usdc_contract.functions.balanceOf(address).call()
print(f"USDC 餘額: {usdc_balance / 10**6}")
# 檢查代幣餘額(應該為 0 或減少)
yes_balance = ctf_contract.functions.balanceOf(
address,
int(yes_position_id, 16)
).call()
no_balance = ctf_contract.functions.balanceOf(
address,
int(no_position_id, 16)
).call()
print(f"Yes 代幣餘額: {yes_balance / 10**6}")
print(f"No 代幣餘額: {no_balance / 10**6}")
if yes_balance == 0 and no_balance == 0:
print("✓ 所有代幣已贖回")
else:
print("⚠ 仍有代幣未贖回")
verify_redemption(your_address, condition_id)
注意事項
重要提示:
- 等待結算:市場必須完全結算後才能贖回
- 爭議期:UMA Oracle 有爭議期,通常需要等待數小時
- 失敗代幣:失敗方的代幣贖回價值為 0
- Gas 費用:需要 MATIC 支付 Gas
- 時間限制:理論上沒有時間限制,但建議儘早贖回
- 檢查結果:確認獲勝方後再贖回,避免 Gas 浪費