diff --git a/src/components/AddressConverter.tsx b/src/components/AddressConverter.tsx new file mode 100644 index 00000000..9358748e --- /dev/null +++ b/src/components/AddressConverter.tsx @@ -0,0 +1,354 @@ +'use client' +import * as React from 'react' +import LucideArrowDownUp from '~icons/lucide/arrow-down-up' +import LucideCheck from '~icons/lucide/check' +import LucideCopy from '~icons/lucide/copy' +import LucideShieldAlert from '~icons/lucide/shield-alert' +import LucideShieldCheck from '~icons/lucide/shield-check' +import { + type CorrectionResult, + correctTempoAddress, + decodeTempoAddress, + encodeTempoAddress, + formatTempoAddress, +} from '../lib/bech32m' +import { Container } from './Container' + +const EXAMPLE_HEX = '0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28' +const EXAMPLE_TEMPO = 'tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0' + +function CopyButton({ text }: { text: string }) { + const [copied, setCopied] = React.useState(false) + + const handleCopy = () => { + navigator.clipboard.writeText(text) + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } + + return ( + + ) +} + +function ConverterSection() { + const [hexInput, setHexInput] = React.useState(EXAMPLE_HEX) + const [tempoInput, setTempoInput] = React.useState('') + const [direction, setDirection] = React.useState<'to-tempo' | 'to-hex'>('to-tempo') + const [result, setResult] = React.useState<{ value: string; error?: string }>({ value: '' }) + + React.useEffect(() => { + try { + if (direction === 'to-tempo') { + if (!hexInput.trim()) { + setResult({ value: '' }) + return + } + const tempo = encodeTempoAddress(hexInput.trim()) + setResult({ value: tempo }) + } else { + if (!tempoInput.trim()) { + setResult({ value: '' }) + return + } + const hex = decodeTempoAddress(tempoInput.trim()) + setResult({ value: hex }) + } + } catch (e) { + setResult({ value: '', error: e instanceof Error ? e.message : 'Invalid input' }) + } + }, [hexInput, tempoInput, direction]) + + const toggleDirection = () => { + if (direction === 'to-tempo' && result.value && !result.error) { + setTempoInput(result.value) + setDirection('to-hex') + } else if (direction === 'to-hex' && result.value && !result.error) { + setHexInput(result.value) + setDirection('to-tempo') + } else { + setDirection((d) => (d === 'to-tempo' ? 'to-hex' : 'to-tempo')) + } + } + + const inputValue = direction === 'to-tempo' ? hexInput : tempoInput + const setInputValue = direction === 'to-tempo' ? setHexInput : setTempoInput + const inputLabel = direction === 'to-tempo' ? '0x Address' : 'tempo1 Address' + const outputLabel = direction === 'to-tempo' ? 'tempo1 Address' : '0x Address' + + return ( +
+
+ +
+ setInputValue(e.target.value)} + placeholder={direction === 'to-tempo' ? '0x...' : 'tempo1...'} + spellCheck={false} + className="w-full bg-transparent font-mono text-[13px] text-gray12 outline-none placeholder:text-gray8" + /> + {inputValue && } +
+
+ +
+ +
+ +
+ {outputLabel} +
+ {result.error ? ( + {result.error} + ) : result.value ? ( +
+ + {result.value} + + +
+ ) : ( + Enter an address above + )} +
+ {result.value && direction === 'to-tempo' && ( +

+ Display: {formatTempoAddress(result.value)} +

+ )} +
+
+ ) +} + +const ERROR_PRESETS = [ + { + label: '1 error', + changes: [{ pos: 12, to: 'x' }], + }, + { + label: '2 errors', + changes: [ + { pos: 8, to: 'm' }, + { pos: 20, to: '6' }, + ], + }, + { + label: '3 errors', + changes: [ + { pos: 8, to: 'm' }, + { pos: 20, to: '6' }, + { pos: 30, to: 'q' }, + ], + }, + { + label: '5 errors', + changes: [ + { pos: 8, to: 'm' }, + { pos: 15, to: 'e' }, + { pos: 22, to: 'x' }, + { pos: 30, to: 'q' }, + { pos: 38, to: 'z' }, + ], + }, +] as const + +function StatusBadge({ result }: { result: CorrectionResult }) { + if (result.status === 'valid') + return ( +
+ + Valid address — no errors +
+ ) + + if (result.status === 'invalid_format') + return ( +
+ + {result.error} +
+ ) + + if (result.status === 'corrected') + return ( +
+
+ + + Corrected — recovered the original address by locating{' '} + {result.errors?.length} error{result.errors?.length !== 1 ? 's' : ''} + {result.searchedErrors === 2 && result.errors?.length === 2 && ' (2-error search)'} + +
+
+
+ {result.corrected} + +
+
+ {result.errors?.map((e) => ( +
+ position {e.position}: {e.was} →{' '} + {e.correctedTo} +
+ ))} +
+
+
+ ) + + return ( +
+ + + Detected — {result.error} + +
+ ) +} + +function ErrorCorrectionDemo() { + const [input, setInput] = React.useState(EXAMPLE_TEMPO) + const [activePreset, setActivePreset] = React.useState(null) + const [correction, setCorrection] = React.useState(null) + const [computing, setComputing] = React.useState(false) + + React.useEffect(() => { + if (!input.trim()) { + setCorrection(null) + return + } + setComputing(true) + // defer to keep UI responsive during 2-error search + const id = setTimeout(() => { + setCorrection(correctTempoAddress(input.trim())) + setComputing(false) + }, 10) + return () => clearTimeout(id) + }, [input]) + + const applyPreset = (idx: number) => { + let addr = EXAMPLE_TEMPO + for (const { pos, to } of ERROR_PRESETS[idx].changes) { + addr = addr.slice(0, pos) + to + addr.slice(pos + 1) + } + setInput(addr) + setActivePreset(idx) + } + + return ( +
+

+ Paste or type any corrupted tempo1 address. The bech32m + checksum will detect the error, and for 1–2 substitutions the algorithm can locate and + recover the original address with no prior knowledge of it. +

+ +
+
+ +
+ {ERROR_PRESETS.map((preset, idx) => ( + + ))} + +
+
+
+ { + setInput(e.target.value) + setActivePreset(null) + }} + placeholder="tempo1..." + spellCheck={false} + className="w-full bg-transparent font-mono text-[13px] text-gray12 outline-none placeholder:text-gray8" + /> +
+
+ + {computing &&
Searching for corrections...
} + {!computing && correction && } +
+ ) +} + +export function AddressConverter() { + const [tab, setTab] = React.useState<'convert' | 'correct'>('convert') + + return ( + + + + + } + > + {tab === 'convert' ? : } + + ) +} + +export default AddressConverter diff --git a/src/lib/bech32m.ts b/src/lib/bech32m.ts new file mode 100644 index 00000000..d574fbab --- /dev/null +++ b/src/lib/bech32m.ts @@ -0,0 +1,462 @@ +const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l' +const BECH32M_CONST = 0x2bc830a3 +const GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + +function polymod(values: number[]): number { + let chk = 1 + for (const v of values) { + const b = chk >> 25 + chk = ((chk & 0x1ffffff) << 5) ^ v + for (let i = 0; i < 5; i++) { + chk ^= (b >> i) & 1 ? GEN[i] : 0 + } + } + return chk +} + +function hrpExpand(hrp: string): number[] { + const ret: number[] = [] + for (const c of hrp) ret.push(c.charCodeAt(0) >> 5) + ret.push(0) + for (const c of hrp) ret.push(c.charCodeAt(0) & 31) + return ret +} + +function verifyChecksum(hrp: string, data: number[]): boolean { + return polymod([...hrpExpand(hrp), ...data]) === BECH32M_CONST +} + +function createChecksum(hrp: string, data: number[]): number[] { + const values = [...hrpExpand(hrp), ...data, 0, 0, 0, 0, 0, 0] + const mod = polymod(values) ^ BECH32M_CONST + return Array.from({ length: 6 }, (_, i) => (mod >> (5 * (5 - i))) & 31) +} + +function convertBits(data: number[], fromBits: number, toBits: number, pad: boolean): number[] { + let acc = 0 + let bits = 0 + const maxv = (1 << toBits) - 1 + const ret: number[] = [] + + for (const value of data) { + acc = (acc << fromBits) | value + bits += fromBits + while (bits >= toBits) { + bits -= toBits + ret.push((acc >> bits) & maxv) + } + } + + if (pad) { + if (bits > 0) ret.push((acc << (toBits - bits)) & maxv) + } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv) !== 0) { + throw new Error('Invalid padding') + } + + return ret +} + +function bech32mEncode(hrp: string, dataBytes: number[]): string { + const data5 = convertBits(dataBytes, 8, 5, true) + const checksum = createChecksum(hrp, data5) + return hrp + '1' + [...data5, ...checksum].map((d) => CHARSET[d]).join('') +} + +function bech32mDecode(addr: string): { hrp: string; data: number[] } { + if (addr.length > 90) throw new Error('Address exceeds 90 characters') + + for (const c of addr) { + const code = c.charCodeAt(0) + if (code < 33 || code > 126) throw new Error('Character outside printable ASCII range') + } + + const hasLower = /[a-z]/.test(addr) + const hasUpper = /[A-Z]/.test(addr) + if (hasLower && hasUpper) throw new Error('Mixed case rejected per BIP-350') + + addr = addr.toLowerCase() + const pos = addr.lastIndexOf('1') + if (pos < 1) throw new Error('No separator character found') + + const hrp = addr.slice(0, pos) + const dataPart = addr.slice(pos + 1) + if (dataPart.length < 6) throw new Error('Data part too short for checksum') + + const data5 = Array.from(dataPart, (c) => CHARSET.indexOf(c)) + if (data5.some((d) => d === -1)) throw new Error('Invalid character in data part') + + if (!verifyChecksum(hrp, data5)) throw new Error('Invalid checksum — address is corrupted') + + const payload = convertBits(data5.slice(0, -6), 5, 8, false) + return { hrp, data: payload } +} + +const CURRENT_VERSION = 0 + +export function encodeTempoAddress(hexAddress: string): string { + const hex = hexAddress.replace(/^0x/i, '') + if (hex.length !== 40 || !/^[0-9a-fA-F]{40}$/.test(hex)) + throw new Error('Invalid hex address: must be 20 bytes (40 hex chars)') + + const rawBytes = Array.from({ length: 20 }, (_, i) => parseInt(hex.slice(i * 2, i * 2 + 2), 16)) + const data = [CURRENT_VERSION, ...rawBytes] + return bech32mEncode('tempo', data) +} + +export function decodeTempoAddress(address: string): string { + const { hrp, data } = bech32mDecode(address) + + if (hrp !== 'tempo') throw new Error(`Invalid HRP: expected "tempo", got "${hrp}"`) + if (data.length < 1) throw new Error('Address data too short') + + const version = data[0] + if (version !== CURRENT_VERSION) throw new Error(`Unsupported version: ${version}`) + + const rawAddress = data.slice(1) + if (rawAddress.length !== 20) + throw new Error(`Invalid address length: ${rawAddress.length} bytes (expected 20)`) + + return '0x' + rawAddress.map((b) => b.toString(16).padStart(2, '0')).join('') +} + +export function validateTempoAddress(address: string): { valid: boolean; error?: string } { + try { + decodeTempoAddress(address) + return { valid: true } + } catch (e) { + return { valid: false, error: e instanceof Error ? e.message : 'Unknown error' } + } +} + +export function formatTempoAddress(address: string): string { + if (address.length < 12) return address + return `${address.slice(0, 6)} ${address.slice(6).replace(/(.{5})/g, '$1 ')}`.trim() +} + +// --------------------------------------------------------------------------- +// Syndrome-based BCH error correction over GF(1024) +// +// Ported from Pieter Wuille's reference implementation (sipa/bech32). +// The bech32m BCH code's syndromes live in GF(1024) = GF(32²). Error +// positions are found algebraically, then values are corrected by targeted +// search at the known positions — O(n) total instead of O(n²×32²) brute force. +// --------------------------------------------------------------------------- + +// prettier-ignore +const GF1024_EXP = [ + 1,32,311,139,206,553,934,180,537,145,910,131,462,373,652,927,675,840,938, + 308,235,958,948,756,1007,979,356,172,281,124,240,222,41,23,736,367,460,309, + 203,649,831,570,454,117,464,693,392,1010,115,272,348,667,383,972,644,671, + 511,610,129,398,818,922,515,977,292,747,15,480,386,690,360,300,1003,851, + 202,681,520,689,264,604,630,513,913,867,1021,403,146,1006,1011,83,39,471, + 597,854,106,560,134,366,492,2,64,583,278,412,370,620,321,315,267,572,262, + 924,707,56,567,102,944,628,577,470,629,609,225,766,687,712,344,539,209,457, + 405,82,7,224,734,920,579,406,50,887,381,908,195,905,99,784,749,207,521,657, + 63,727,696,40,55,983,484,258,796,877,573,294,683,584,246,30,960,772,109,720, + 600,758,943,404,114,304,107,528,433,485,290,555,998,755,783,269,764,751,143, + 78,903,419,933,212,361,268,732,984,4,128,430,517,785,717,504,642,607,534, + 369,524,561,166,89,359,204,617,481,418,901,483,482,450,245,126,176,665,319, + 395,914,771,141,14,448,181,569,422,773,77,999,723,568,390,562,198,809,250, + 414,306,43,87,167,121,80,71,679,968,516,817,1018,371,588,118,432,453,21, + 672,808,218,169,441,229,638,769,205,585,214,297,843,970,580,374,748,239,830, + 538,241,254,286,156,558,838,618,385,722,536,177,697,8,256,860,298,811,186, + 985,36,439,293,715,312,363,332,155,718,408,498,962,836,554,966,964,900,451, + 213,329,59,599,790,557,806,282,28,896,323,379,844,810,154,750,175,377,780, + 365,396,882,477,789,589,86,135,334,219,137,142,110,688,296,875,765,719,440, + 197,841,906,3,96,880,413,338,859,458,501,802,410,434,389,594,950,692,424, + 709,248,478,885,317,459,469,533,273,380,940,500,770,173,313,331,123,16,512, + 945,596,886,349,699,72,839,586,182,601,726,664,287,188,793,973,676,936,372, + 684,680,552,902,387,658,95,423,805,378,876,541,17,544,646,735,952,884,285, + 252,350,731,824,730,792,1005,915,803,442,133,270,668,415,274,284,220,105, + 592,1014,243,190,857,394,946,564,6,192,1001,787,653,959,916,963,868,797,845, + 778,429,613,97,848,170,473,917,995,595,918,899,291,523,721,632,961,804,346, + 603,662,223,9,288,619,417,997,659,127,144,942,436,325,443,165,57,535,337, + 827,698,104,624,705,120,112,368,556,774,45,151,846,874,733,1016,307,11,352, + 44,183,633,993,531,465,661,191,889,189,825,762,559,870,861,266,540,49,791, + 525,529,401,210,425,741,463,341,955,788,621,353,12,384,754,815,58,631,545, + 678,1000,819,954,820,858,490,194,937,340,923,547,742,431,549,550,582,310, + 171,505,674,872,669,447,37,407,18,576,502,834,746,47,215,265,636,833,650, + 863,330,91,295,651,895,125,208,489,162,217,201,713,376,812,90,263,956,1012, + 179,761,591,22,704,88,327,507,738,303,907,35,343,1019,339,891,253,382,1004, + 947,532,305,75,807,314,299,779,397,850,234,926,643,639,801,506,706,24,768, + 237,894,93,487,354,108,752,879,637,865,957,980,388,626,641,575,358,236,862, + 362,364,428,581,342,987,100,1008,51,855,74,775,13,416,965,932,244,94,391, + 530,497,930,52,951,660,159,590,54,1015,211,393,978,324,411,402,178,729,888, + 157,526,625,737,335,251,446,5,160,153,654,991,228,606,566,70,647,767,655, + 1023,467,725,760,623,289,587,150,878,605,598,822,794,941,468,565,38,503,866, + 989,164,25,800,474,1013,147,974,708,216,233,1022,499,994,627,673,776,493,34, + 375,716,472,949,724,728,856,426,645,703,200,745,79,935,148,814,26,832,682, + 616,449,149,782,301,971,612,65,615,33,279,444,69,743,399,786,685,648,799, + 781,333,187,1017,275,316,491,226,670,479,853,10,320,283,60,695,456,437,357, + 140,46,247,62,759,911,163,249,510,578,438,261,1020,435,421,869,829,634,897, + 355,76,967,996,691,328,27,864,925,739,271,700,168,409,466,757,975,740,495, + 98,816,986,68,711,184,921,611,161,185,953,852,42,119,400,242,158,622,257, + 892,29,928,116,496,898,259,828,602,694,488,130,494,66,519,849,138,238,798, + 813,122,48,823,826,666,351,763,527,593,982,452,53,919,931,20,640,543,81,103, + 912,835,714,280,92,455,85,231,574,326,475,981,420,837,522,753,847,842,1002, + 883,509,546,710,152,686,744,111,656,31,992,563,230,542,113,336,795,909,227, + 702,232,990,196,873,701,136,174,345,571,486,322,347,635,929,84,199,777,461, + 277,508,514,1009,19,608,193,969,548,518,881,445,101,976,260,988,132,302,939, + 276,476,821,890,221,73,871,893,61,663,255,318,427,677,904,67,551,614, +] + +// prettier-ignore +const GF1024_LOG = [ + -1,0,99,363,198,726,462,132,297,495,825,528,561,693,231,66,396,429,594,990, + 924,264,627,33,660,759,792,858,330,891,165,957,1,804,775,635,304,592,754,90, + 153,32,883,248,530,521,834,599,911,547,138,689,703,921,708,154,113,508,565, + 324,828,1013,836,150,100,802,903,1020,874,807,734,253,403,1010,691,646,853, + 237,189,788,252,927,131,89,982,935,347,249,629,212,620,607,933,664,698,423, + 364,476,871,144,687,998,115,928,513,453,94,176,667,168,353,955,517,962,174, + 48,893,43,261,884,516,251,910,395,29,611,223,501,199,58,901,11,1002,446,96, + 348,973,351,906,3,833,230,352,188,502,9,86,763,790,797,745,522,952,728,336, + 311,288,719,887,706,727,879,614,839,758,507,211,250,864,268,478,586,27,392, + 974,338,224,295,716,624,7,233,406,531,876,880,302,816,411,539,457,537,463, + 992,575,142,970,360,243,983,786,616,74,38,214,273,4,147,612,128,552,710,193, + 322,275,600,766,615,267,350,452,1009,31,494,133,122,821,966,731,270,960,936, + 968,767,653,20,679,662,907,282,30,285,886,456,697,222,164,835,380,840,245, + 724,436,640,286,1015,298,889,157,896,1000,844,110,621,78,601,545,108,195, + 185,447,862,49,387,450,818,1005,986,102,805,932,28,329,827,451,435,287,410, + 496,743,180,485,64,306,161,608,355,276,300,649,71,799,1003,633,175,645,247, + 527,19,37,585,2,308,393,648,107,819,383,1016,226,826,106,978,332,713,505, + 938,630,857,323,606,394,310,815,349,723,963,510,367,638,577,556,685,636,126, + 975,491,979,50,401,437,915,529,560,666,852,26,832,678,213,70,194,681,309, + 682,341,97,35,518,208,104,259,416,13,280,776,618,339,426,333,388,140,641,52, + 562,292,68,421,674,374,241,699,46,711,459,227,342,651,59,809,885,551,715,85, + 173,130,137,593,313,865,372,714,103,366,246,449,694,498,217,191,941,847,235, + 424,378,553,783,1017,683,474,200,581,262,178,373,846,504,831,843,305,359, + 269,445,506,806,997,725,591,232,796,221,321,920,263,42,934,830,129,369,384, + 36,985,12,555,44,535,866,739,752,385,119,91,778,479,761,939,1006,344,381, + 823,67,216,220,219,156,179,977,665,900,613,574,820,98,774,902,870,894,701, + 314,769,390,370,596,755,204,587,658,631,987,949,841,56,397,81,988,62,256, + 201,995,904,76,148,943,486,209,549,720,917,177,550,700,534,644,386,207,509, + 294,8,284,127,546,428,961,926,430,567,950,579,994,582,583,1021,419,5,317, + 181,519,327,289,542,95,210,242,959,461,753,733,114,240,234,41,976,109,160, + 937,677,595,118,842,136,279,684,584,101,163,274,405,744,260,346,707,626,454, + 918,375,482,399,92,748,325,170,407,898,492,79,747,732,206,991,121,57,878, + 801,475,1022,803,795,215,291,497,105,559,888,742,514,721,675,771,117,120,80, + 566,488,532,850,980,602,670,271,656,925,676,205,655,54,784,431,735,812,39, + 604,609,14,466,729,737,956,149,422,500,705,536,493,1014,409,225,914,51,448, + 590,822,55,265,772,588,16,414,1018,568,254,418,75,794,162,417,811,953,124, + 354,77,69,856,377,45,899,829,152,296,512,402,863,972,967,785,628,515,659, + 112,765,379,951,875,125,617,931,307,777,203,312,358,169,487,293,239,780,740, + 408,151,781,717,440,438,196,525,134,432,34,722,632,861,869,554,580,808,954, + 787,598,65,281,146,337,187,668,944,563,183,23,867,171,837,741,625,541,916, + 186,357,123,736,661,272,391,229,167,236,520,692,773,984,473,650,340,814,798, + 184,145,202,810,465,558,345,326,548,441,412,750,964,158,471,908,813,760,657, + 371,444,490,425,328,647,266,244,335,301,619,909,791,564,872,257,60,570,572, + 1007,749,912,439,540,913,511,897,849,283,40,793,603,597,930,316,942,290,404, + 17,361,946,277,334,472,523,945,477,905,652,73,882,824,93,690,782,458,573, + 368,299,544,680,605,859,671,756,83,470,848,543,1011,589,971,524,356,427,159, + 746,669,365,996,343,948,434,382,400,139,718,538,1008,639,890,1012,663,610, + 331,851,895,484,320,218,420,190,1019,143,362,634,141,965,10,838,929,82,228, + 443,468,480,483,922,135,877,61,578,111,860,654,15,892,981,702,923,696,192,6, + 789,415,576,18,1004,389,751,503,172,116,398,460,643,22,779,376,704,433,881, + 571,557,622,672,21,467,166,489,315,469,319,695,318,854,255,993,278,800,53, + 413,764,868,999,63,712,25,673,940,919,155,197,303,873,686,1001,757,969,730, + 958,533,770,481,855,499,182,238,569,464,947,72,642,442,87,24,688,989,47,88, + 623,762,455,709,526,817,258,637,845,84,768,738, +] + +/** + * Maps a 30-bit polymod residue to three 10-bit syndromes in GF(1024). + * The syndrome transform is specific to the bech32/bech32m generator polynomial. + */ +function computeSyndrome(residue: number): number { + const low = residue & 0x1f + let syn = low ^ (low << 10) ^ (low << 20) + /* prettier-ignore */ + const BITS = [ + 0x3d0195bd, 0x2a932b53, 0x072653af, 0x0cdc067e, 0x19ac89f5, + 0x15922369, 0x29b443f2, 0x03f826ed, 0x0574c8fa, 0x087935dd, + 0x2c6dcf4f, 0x0acfbfbe, 0x158bfa75, 0x2993d5e3, 0x03b70fc6, + 0x051e8fd6, 0x08b99aa5, 0x1167b06a, 0x205f60d4, 0x12aae581, + 0x04296874, 0x0846f4c1, 0x108d4d82, 0x210ebf04, 0x1299fb28, + ] + for (let i = 0; i < 25; i++) { + if ((residue >> (i + 5)) & 1) syn ^= BITS[i] + } + return syn +} + +/** + * Locates up to 2 error positions in the data part using syndrome decoding + * over GF(1024). Returns positions as offsets from the END of the data + * (position 0 = last character). Returns [] if errors cannot be located. + */ +function locateErrors(residue: number, length: number): number[] { + if (residue === 0) return [] + + const syn = computeSyndrome(residue) + const s0 = syn & 0x3ff + const s1 = (syn >> 10) & 0x3ff + const s2 = syn >> 20 + const l_s0 = GF1024_LOG[s0] + const l_s1 = GF1024_LOG[s1] + const l_s2 = GF1024_LOG[s2] + + if (l_s0 !== -1 && l_s1 !== -1 && l_s2 !== -1) { + if ((2 * l_s1 - l_s2 - l_s0 + 2046) % 1023 === 0) { + const p1 = (l_s1 - l_s0 + 1023) % 1023 + if (p1 < length) { + const l_e1 = l_s0 + (1023 - 997) * p1 + if (l_e1 % 33 === 0) return [p1] + } + } + } + + for (let p1 = 0; p1 < length; p1++) { + const s2_s1p1 = s2 ^ (s1 === 0 ? 0 : GF1024_EXP[(l_s1 + p1) % 1023]) + if (s2_s1p1 === 0) continue + const s1_s0p1 = s1 ^ (s0 === 0 ? 0 : GF1024_EXP[(l_s0 + p1) % 1023]) + if (s1_s0p1 === 0) continue + const l_s1_s0p1 = GF1024_LOG[s1_s0p1] + const p2 = (GF1024_LOG[s2_s1p1] - l_s1_s0p1 + 1023) % 1023 + if (p2 >= length || p1 === p2) continue + const s1_s0p2 = s1 ^ (s0 === 0 ? 0 : GF1024_EXP[(l_s0 + p2) % 1023]) + if (s1_s0p2 === 0) continue + const inv_p1_p2 = 1023 - GF1024_LOG[GF1024_EXP[p1] ^ GF1024_EXP[p2]] + const l_e2 = l_s1_s0p1 + inv_p1_p2 + (1023 - 997) * p2 + if (l_e2 % 33 !== 0) continue + const l_e1 = GF1024_LOG[s1_s0p2] + inv_p1_p2 + (1023 - 997) * p1 + if (l_e1 % 33 !== 0) continue + return p1 < p2 ? [p1, p2] : [p2, p1] + } + + return [] +} + +export type CorrectionResult = { + status: 'valid' | 'corrected' | 'detected' | 'invalid_format' + corrected?: string + errors?: { position: number; was: string; correctedTo: string }[] + candidates?: number + searchedErrors?: number + error?: string +} + +/** + * Attempts to correct a corrupted tempo1 address using syndrome-based BCH + * decoding (ported from sipa/bech32). Algebraically locates error positions + * via GF(1024) syndromes, then tests values only at those positions. + * O(n) for position finding + O(32^k) for k located errors. + */ +export function correctTempoAddress(address: string): CorrectionResult { + const addr = address.toLowerCase() + + if (addr.length > 90) + return { status: 'invalid_format', error: 'Address exceeds 90 characters' } + if (/[A-Z]/.test(address) && /[a-z]/.test(address)) + return { status: 'invalid_format', error: 'Mixed case rejected per BIP-350' } + + const sepPos = addr.lastIndexOf('1') + if (sepPos < 1) return { status: 'invalid_format', error: 'No separator found' } + + const hrp = addr.slice(0, sepPos) + const dataPart = addr.slice(sepPos + 1) + if (dataPart.length < 6) + return { status: 'invalid_format', error: 'Data part too short' } + + const data5 = Array.from(dataPart, (c) => CHARSET.indexOf(c)) + if (data5.some((d) => d === -1)) + return { status: 'invalid_format', error: 'Invalid character in data part' } + + const expanded = hrpExpand(hrp) + const residue = polymod([...expanded, ...data5]) ^ BECH32M_CONST + + if (residue === 0) { + try { + decodeTempoAddress(address) + return { status: 'valid' } + } catch (e) { + return { status: 'invalid_format', error: e instanceof Error ? e.message : 'Invalid' } + } + } + + const errorPositions = locateErrors(residue, dataPart.length) + + if (errorPositions.length === 0) { + return { + status: 'detected', + searchedErrors: 2, + error: 'Errors detected but positions could not be determined (likely 3+ substitutions)', + } + } + + // Convert from end-relative positions to data-part indices + const dataLen = dataPart.length + const indices = errorPositions.map((p) => dataLen - 1 - p) + + // Try all character values at the located positions + const originals = indices.map((i) => data5[i]) + + if (indices.length === 1) { + const [idx] = indices + const orig = originals[0] + for (let c = 0; c < 32; c++) { + if (c === orig) continue + data5[idx] = c + if (polymod([...expanded, ...data5]) === BECH32M_CONST) { + const fixed = addr.slice(0, sepPos + 1 + idx) + CHARSET[c] + addr.slice(sepPos + 1 + idx + 1) + data5[idx] = orig + try { + decodeTempoAddress(fixed) + return { + status: 'corrected', + corrected: fixed, + errors: [{ position: sepPos + 1 + idx, was: CHARSET[orig], correctedTo: CHARSET[c] }], + candidates: 1, + searchedErrors: 1, + } + } catch { + return { status: 'detected', error: 'Position located but no valid Tempo address found' } + } + } + } + data5[idx] = orig + } + + if (indices.length === 2) { + const [idx0, idx1] = indices + const [orig0, orig1] = originals + for (let c0 = 0; c0 < 32; c0++) { + if (c0 === orig0) continue + data5[idx0] = c0 + for (let c1 = 0; c1 < 32; c1++) { + if (c1 === orig1) continue + data5[idx1] = c1 + if (polymod([...expanded, ...data5]) === BECH32M_CONST) { + const parts = [...addr] + parts[sepPos + 1 + idx0] = CHARSET[c0] + parts[sepPos + 1 + idx1] = CHARSET[c1] + const fixed = parts.join('') + data5[idx0] = orig0 + data5[idx1] = orig1 + try { + decodeTempoAddress(fixed) + return { + status: 'corrected', + corrected: fixed, + errors: [ + { position: sepPos + 1 + idx0, was: CHARSET[orig0], correctedTo: CHARSET[c0] }, + { position: sepPos + 1 + idx1, was: CHARSET[orig1], correctedTo: CHARSET[c1] }, + ], + candidates: 1, + searchedErrors: 2, + } + } catch { + return { status: 'detected', error: 'Positions located but no valid Tempo address found' } + } + } + } + } + data5[idx0] = orig0 + data5[idx1] = orig1 + } + + return { + status: 'detected', + searchedErrors: 2, + error: 'Errors detected but correction failed', + } +} diff --git a/src/pages/guide/issuance/create-a-stablecoin.mdx b/src/pages/guide/issuance/create-a-stablecoin.mdx index c3f2c7fa..5952bc97 100644 --- a/src/pages/guide/issuance/create-a-stablecoin.mdx +++ b/src/pages/guide/issuance/create-a-stablecoin.mdx @@ -37,7 +37,7 @@ Ensure that you have set up your project with Wagmi and integrated accounts by f Before we send off a transaction to deploy our stablecoin to the Tempo testnet, we need to make sure our account is funded with a stablecoin to cover the transaction fee. -As we have configured our project to use `AlphaUSD` (`0x20c000…0001`) +As we have configured our project to use `AlphaUSD` (`tempo1qqsv…9xgnd`) as the [default fee token](/quickstart/integrate-tempo#default-fee-token), we will need to add some `AlphaUSD` to our account. Luckily, the built-in Tempo testnet faucet supports funding accounts with `AlphaUSD`. diff --git a/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx b/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx index c3b3dd28..8aa4ab6f 100644 --- a/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx +++ b/src/pages/guide/payments/pay-fees-in-any-stablecoin.mdx @@ -88,7 +88,7 @@ Ensure that you have set up your project with Wagmi and integrated accounts by f ### Add testnet funds¹ -Before you can pay fees in a token of your choice, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`) and paying fees in `BetaUSD` (`0x20c000…0002`). +Before you can pay fees in a token of your choice, you need to fund your account. In this guide you will be sending `AlphaUSD` (`tempo1qqsv…9xgnd`) and paying fees in `BetaUSD` (`tempo1qqsv…mqzz2`). The built-in Tempo testnet faucet includes `AlphaUSD` and `BetaUSD` when funding. @@ -272,7 +272,7 @@ For simplicity of the guide, this example uses a Private Key (Secp256k1) account ### Add testnet funds¹ -Before you can pay fees in a token of your choice, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`) and paying fees in `BetaUSD` (`0x20c000…0002`). +Before you can pay fees in a token of your choice, you need to fund your account. In this guide you will be sending `AlphaUSD` (`tempo1qqsv…9xgnd`) and paying fees in `BetaUSD` (`tempo1qqsv…mqzz2`). The built-in Tempo testnet faucet includes `AlphaUSD` and `BetaUSD` when funding. diff --git a/src/pages/guide/payments/send-a-payment.mdx b/src/pages/guide/payments/send-a-payment.mdx index 7eaaa891..1f25b0d8 100644 --- a/src/pages/guide/payments/send-a-payment.mdx +++ b/src/pages/guide/payments/send-a-payment.mdx @@ -33,7 +33,7 @@ Ensure that you have set up your project with Wagmi and integrated accounts by f ### Add testnet funds¹ -Before you can send a payment, you need to fund your account. In this guide you will be sending `AlphaUSD` (`0x20c000…0001`). +Before you can send a payment, you need to fund your account. In this guide you will be sending `AlphaUSD` (`tempo1qqsv…9xgnd`). The built-in Tempo testnet faucet funds accounts with `AlphaUSD`. diff --git a/src/pages/guide/use-accounts/add-funds.mdx b/src/pages/guide/use-accounts/add-funds.mdx index fcced800..0b716cd0 100644 --- a/src/pages/guide/use-accounts/add-funds.mdx +++ b/src/pages/guide/use-accounts/add-funds.mdx @@ -82,10 +82,10 @@ The faucet funds the following assets. | Asset | Address |Amount| |-------|---------|----:| -| [pathUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000000) | `0x20c0000000000000000000000000000000000000` | `1M` | -| [AlphaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000001) | `0x20c0000000000000000000000000000000000001` | `1M` | -| [BetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000002) | `0x20c0000000000000000000000000000000000002` | `1M` | -| [ThetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000003) | `0x20c0000000000000000000000000000000000003` | `1M` | +| [pathUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000000) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv0ywuh` | `1M` | +| [AlphaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000001) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqyr9xgnd` | `1M` | +| [BetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000002) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqgjmqzz2` | `1M` | +| [ThetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000003) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqva3zyds` | `1M` | ## Verify Your Balance diff --git a/src/pages/protocol/addresses.mdx b/src/pages/protocol/addresses.mdx new file mode 100644 index 00000000..a8b23277 --- /dev/null +++ b/src/pages/protocol/addresses.mdx @@ -0,0 +1,120 @@ +--- +title: Tempo Address Format +description: Convert between 0x and tempo1 address formats. Tempo uses bech32m encoding (BIP-350) for human-readable addresses with built-in error detection. +--- + +import { AddressConverter } from '../../components/AddressConverter' + +# Tempo Address Format + +Tempo addresses use [bech32m encoding](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) (BIP-350) to provide a human-readable format that is instantly distinguishable from addresses on other chains. All Tempo addresses begin with the `tempo1` prefix and are exactly 46 characters long. + +``` +tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0 +``` + +## Address Converter + +Convert between standard `0x` hex addresses and `tempo1` bech32m addresses. Use the **Error Correction** tab to see how the bech32m checksum detects corrupted addresses and recovers the original by locating errors — with no prior knowledge of the correct address. + + + +## Format Overview + +A Tempo address encodes a 20-byte Ethereum-style address with a version byte, a human-readable prefix, and a 6-character checksum: + +``` +┌─────────┬───┬──────────────────────────────────────────────────────┐ +│ HRP │ 1 │ Base32-encoded data + checksum │ +│ "tempo" │sep│ │ +└─────────┴───┴──────────────────────────────────────────────────────┘ + │ + ┌───────────────────┴──────────────────────────┐ + │ Data (base32) │ + ├─────────┬──────────────┬─────────────────────┤ + │ Version │ Raw Address │ Bech32m Checksum │ + │ 1 byte │ 20 bytes │ 6 chars │ + └─────────┴──────────────┴─────────────────────┘ +``` + +| Property | Value | +|----------|-------| +| HRP (Human-Readable Part) | `tempo` | +| Full prefix | `tempo1` | +| Version byte | `0x00` (current) | +| Raw address | 20 bytes (Ethereum-style) | +| Total length | 46 characters | +| Encoding | bech32m (BIP-350) | + +## Error Detection and Correction + +The bech32m checksum guarantees detection of: + +- **Up to 4 character substitutions** anywhere in the address +- **Insertions and deletions** via checksum (≥ 1 − 2⁻³⁰ probability) plus fixed-length validation + +Beyond detection, the BCH code structure enables **error correction** — the algorithm can locate the corrupted characters and recover the original address with no prior knowledge of it: + +- **1 substitution**: always correctable (unique solution found) +- **2 substitutions**: correctable in most cases (searches ~800K candidates) +- **3–4 substitutions**: detected but too many candidates to correct reliably +- **5+ substitutions**: probabilistic detection (≥ 1 − 2⁻³⁰) + +Try it in the **Error Correction** tab above to see recovery in action. + +## Case Handling + +Per BIP-350, Tempo addresses are case-insensitive: + +- Encoders produce **lowercase** (`tempo1qp6z...`) +- Decoders accept all-lowercase or all-uppercase +- **Mixed case is rejected** — `TeMpO1qp6z...` is invalid + +## Display Recommendations + +For readability, UIs may display addresses with visual spacing: + +

tempo1 qp6z6 dwvvc 6vq5e fyk3m s39un e6etu 4a9qt j2kk0

+ +Spaces are for **display only** — copy buttons and APIs must always use the canonical form without spaces. + +## JavaScript / TypeScript Usage + +The `src/lib/bech32m.ts` module provides encode, decode, and validate functions: + +```typescript +import { + encodeTempoAddress, + decodeTempoAddress, + validateTempoAddress, +} from './lib/bech32m' + +// 0x → tempo1 +const tempo = encodeTempoAddress('0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28') +// → "tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0" + +// tempo1 → 0x +const hex = decodeTempoAddress('tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kk0') +// → "0x742d35cc6634c0532925a3b844bc9e7595f2bd28" + +// Validate without throwing +const result = validateTempoAddress('tempo1qp6z6dwvvc6vq5efyk3ms39une6etu4a9qtj2kkq') +// → { valid: false, error: "Invalid checksum — address is corrupted" } +``` + +## Specification + +The full specification is defined in [TIP-1023](/protocol/tips/tip-1023), which covers: + +- Complete encoding and decoding algorithms +- Version byte semantics and extensibility +- Validation rules (8 steps) +- Invariants (9 properties) +- Python reference implementation +- Test vectors for valid addresses, invalid versions, incorrect lengths, and substitution detection + +## Source Code + +- [TIP-1023 Specification](https://github.com/tempoxyz/tempo/pull/2925) — PR in `tempoxyz/tempo` +- [TypeScript Implementation](https://github.com/tempoxyz/docs/blob/main/src/lib/bech32m.ts) — `bech32m.ts` in this docs repo +- [BIP-350 (bech32m)](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) — The underlying encoding standard diff --git a/src/pages/protocol/exchange/index.mdx b/src/pages/protocol/exchange/index.mdx index aa48036a..10e47835 100644 --- a/src/pages/protocol/exchange/index.mdx +++ b/src/pages/protocol/exchange/index.mdx @@ -8,7 +8,7 @@ import { Cards, Card } from 'vocs' Tempo features an enshrined decentralized exchange (DEX) designed specifically for trading between stablecoins of the same underlying asset (e.g., USDC to USDT). The exchange provides optimal pricing for cross-stablecoin payments while minimizing chain load from excessive market activity. -The exchange operates as a singleton precompiled contract at address `0xdec0000000000000000000000000000000000000`. It maintains an orderbook with separate queues for each price tick, using price-time priority for order matching. +The exchange operates as a singleton precompiled contract at address `tempo1qr0vqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqvml629`. It maintains an orderbook with separate queues for each price tick, using price-time priority for order matching. Trading pairs are determined by each token's quote token. All TIP-20 tokens specify a quote token for trading on the exchange. See [Quote Tokens](/protocol/exchange/quote-tokens) for more information on quote token selection and the optional [pathUSD](/protocol/exchange/quote-tokens#pathusd) stablecoin. See the [Stablecoin DEX Specification](/protocol/exchange/spec) for detailed information on the exchange structure. diff --git a/src/pages/protocol/exchange/quote-tokens.mdx b/src/pages/protocol/exchange/quote-tokens.mdx index 15091e0b..bafd6b79 100644 --- a/src/pages/protocol/exchange/quote-tokens.mdx +++ b/src/pages/protocol/exchange/quote-tokens.mdx @@ -22,7 +22,7 @@ pathUSD is a predeployed [TIP-20](/protocol/tip20/spec) at genesis. Since it is | Property | Value | | -------------- | -------------------------------------------- | -| address | `0x20c0000000000000000000000000000000000000` | +| address | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv0ywuh` | | `name()` | `"pathUSD"` | | `symbol()` | `"pathUSD"` | | `currency()` | `"USD"` | diff --git a/src/pages/protocol/exchange/spec.mdx b/src/pages/protocol/exchange/spec.mdx index a940eee2..4dea2620 100644 --- a/src/pages/protocol/exchange/spec.mdx +++ b/src/pages/protocol/exchange/spec.mdx @@ -24,7 +24,7 @@ Another design goal is to avoid fragmentation of liquidity across many different ### Contract and scope -The exchange is a singleton contract deployed at `0xdec0000000000000000000000000000000000000`. It exposes functions to create trading pairs, place and cancel orders (including flip orders), execute swaps, produce quotes, and manage internal balances. +The exchange is a singleton contract deployed at `tempo1qr0vqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqvml629`. It exposes functions to create trading pairs, place and cancel orders (including flip orders), execute swaps, produce quotes, and manage internal balances. ### Key concepts diff --git a/src/pages/protocol/fees/spec-fee-amm.mdx b/src/pages/protocol/fees/spec-fee-amm.mdx index b74bef4d..5f0efecc 100644 --- a/src/pages/protocol/fees/spec-fee-amm.mdx +++ b/src/pages/protocol/fees/spec-fee-amm.mdx @@ -140,7 +140,7 @@ Verifies sufficient validator token reserves for the fee swap. Calculates `maxAm #### 2. FeeManager Contract -Tempo introduces a precompiled contract, the `FeeManager`, at the address `0xfeec000000000000000000000000000000000000`. +Tempo introduces a precompiled contract, the `FeeManager`, at the address `tempo1qrlwcqqqqqqqqqqqqqqqqqqqqqqqqqqqqqx3a39m`. The `FeeManager` is a singleton contract that implements all the functions of the Fee AMM for every pool. It handles the collection and refunding of fees during each transaction, executes fee swaps immediately, stores fee token preferences for users and validators, and accumulates fees for validators to claim via `distributeFees()`. diff --git a/src/pages/protocol/tip20/spec.mdx b/src/pages/protocol/tip20/spec.mdx index 026414e1..45a4de8e 100644 --- a/src/pages/protocol/tip20/spec.mdx +++ b/src/pages/protocol/tip20/spec.mdx @@ -445,7 +445,7 @@ System level functions `systemTransferFrom`, `transferFeePreTx`, and `transferFe See [rewards distribution](/protocol/tip20-rewards/spec) for more information. ## TIP20Factory -The `TIP20Factory` contract is the canonical entrypoint for creating new TIP-20 tokens on Tempo. The factory derives deterministic deployment addresses using a caller-provided salt, combined with the caller's address, under a fixed 12-byte TIP-20 prefix. This ensures that every TIP-20 token exists at a predictable, collision-free address. The `TIP20Factory` precompile is deployed at `0x20Fc000000000000000000000000000000000000`. +The `TIP20Factory` contract is the canonical entrypoint for creating new TIP-20 tokens on Tempo. The factory derives deterministic deployment addresses using a caller-provided salt, combined with the caller's address, under a fixed 12-byte TIP-20 prefix. This ensures that every TIP-20 token exists at a predictable, collision-free address. The `TIP20Factory` precompile is deployed at `tempo1qqs0cqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkm2wnu`. Newly created TIP-20 addresses are deployed to a deterministic address derived from `TIP20_PREFIX || lowerBytes`, where: - `TIP20_PREFIX` is the 12-byte prefix `20C000000000000000000000` diff --git a/src/pages/protocol/tip403/spec.mdx b/src/pages/protocol/tip403/spec.mdx index d60e5b8b..3ef38f99 100644 --- a/src/pages/protocol/tip403/spec.mdx +++ b/src/pages/protocol/tip403/spec.mdx @@ -20,7 +20,7 @@ TIP-403 addresses this by providing a centralized registry that tokens can refer The TIP-403 registry stores policies that TIP-20 tokens check against on any token transfer. Policies are associated with a unique `policyId`, can either be a blacklist or a whitelist policy, and contain a list of addresses. This list of addresses can be updated by the policy `admin`. -The TIP403Registry is deployed at address `0x403c000000000000000000000000000000000000`. +The TIP403Registry is deployed at address `tempo1qpqrcqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr08uee`. ## Built-in Policies diff --git a/src/pages/protocol/transactions/AccountKeychain.mdx b/src/pages/protocol/transactions/AccountKeychain.mdx index 71f0438b..734b7544 100644 --- a/src/pages/protocol/transactions/AccountKeychain.mdx +++ b/src/pages/protocol/transactions/AccountKeychain.mdx @@ -4,7 +4,7 @@ description: Technical specification for the Account Keychain precompile managin # Account Keychain Precompile -**Address:** `0xAAAAAAAA00000000000000000000000000000000` +**Address:** `tempo1qz424242qqqqqqqqqqqqqqqqqqqqqqqqqqgsxxqr` ## Overview diff --git a/src/pages/protocol/transactions/spec-tempo-transaction.mdx b/src/pages/protocol/transactions/spec-tempo-transaction.mdx index 38742ca3..d30d0edf 100644 --- a/src/pages/protocol/transactions/spec-tempo-transaction.mdx +++ b/src/pages/protocol/transactions/spec-tempo-transaction.mdx @@ -587,7 +587,7 @@ Note: `expiry` and `limits` use RLP trailing field semantics - they can be omitt #### Keychain Precompile -The Account Keychain precompile (deployed at address `0xAAAAAAAA00000000000000000000000000000000`) manages authorized access keys for accounts. It enables root keys to provision scoped access keys with expiry timestamps and per-TIP20 token spending limits. +The Account Keychain precompile (deployed at address `tempo1qz424242qqqqqqqqqqqqqqqqqqqqqqqqqqgsxxqr`) manages authorized access keys for accounts. It enables root keys to provision scoped access keys with expiry timestamps and per-TIP20 token spending limits. **See the [Account Keychain Specification](./AccountKeychain) for complete interface details, storage layout, and implementation.** diff --git a/src/pages/quickstart/faucet.mdx b/src/pages/quickstart/faucet.mdx index 26e6b5a6..dbaa1d64 100644 --- a/src/pages/quickstart/faucet.mdx +++ b/src/pages/quickstart/faucet.mdx @@ -83,7 +83,7 @@ The faucet funds the following assets. | Asset | Address |Amount| |-------|---------|----:| -| [pathUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000000) | `0x20c0000000000000000000000000000000000000` | `1M` | -| [AlphaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000001) | `0x20c0000000000000000000000000000000000001` | `1M` | -| [BetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000002) | `0x20c0000000000000000000000000000000000002` | `1M` | -| [ThetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000003) | `0x20c0000000000000000000000000000000000003` | `1M` | +| [pathUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000000) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv0ywuh` | `1M` | +| [AlphaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000001) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqyr9xgnd` | `1M` | +| [BetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000002) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqgjmqzz2` | `1M` | +| [ThetaUSD](https://explore.tempo.xyz/address/0x20c0000000000000000000000000000000000003) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqva3zyds` | `1M` | diff --git a/src/pages/quickstart/predeployed-contracts.mdx b/src/pages/quickstart/predeployed-contracts.mdx index b3648038..36401525 100644 --- a/src/pages/quickstart/predeployed-contracts.mdx +++ b/src/pages/quickstart/predeployed-contracts.mdx @@ -10,11 +10,11 @@ Core protocol contracts that power Tempo's features. | Contract | Address | Description | |----------|---------|-------------| -| [**TIP-20 Factory**](/protocol/tip20/overview) | `0x20fc000000000000000000000000000000000000` | Create new TIP-20 tokens | -| [**Fee Manager**](/protocol/fees/spec-fee-amm#2-feemanager-contract) | `0xfeec000000000000000000000000000000000000` | Handle fee payments and conversions | -| [**Stablecoin DEX**](/protocol/exchange) | `0xdec0000000000000000000000000000000000000` | Enshrined DEX for stablecoin swaps | -| [**TIP-403 Registry**](/protocol/tip403/spec) | `0x403c000000000000000000000000000000000000` | Transfer policy registry | -| [**pathUSD**](/protocol/exchange/quote-tokens#pathusd) | `0x20c0000000000000000000000000000000000000` | First stablecoin deployed | +| [**TIP-20 Factory**](/protocol/tip20/overview) | `tempo1qqs0cqqqqqqqqqqqqqqqqqqqqqqqqqqqqqkm2wnu` | Create new TIP-20 tokens | +| [**Fee Manager**](/protocol/fees/spec-fee-amm#2-feemanager-contract) | `tempo1qrlwcqqqqqqqqqqqqqqqqqqqqqqqqqqqqqx3a39m` | Handle fee payments and conversions | +| [**Stablecoin DEX**](/protocol/exchange) | `tempo1qr0vqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqvml629` | Enshrined DEX for stablecoin swaps | +| [**TIP-403 Registry**](/protocol/tip403/spec) | `tempo1qpqrcqqqqqqqqqqqqqqqqqqqqqqqqqqqqqr08uee` | Transfer policy registry | +| [**pathUSD**](/protocol/exchange/quote-tokens#pathusd) | `tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv0ywuh` | First stablecoin deployed | ## Standard Utilities @@ -22,11 +22,11 @@ Popular Ethereum contracts deployed for convenience. | Contract | Address | Description | |----------|---------|-------------| -| [**Multicall3**](https://www.multicall3.com/) | `0xcA11bde05977b3631167028862bE2a173976CA11` | Batch multiple calls in one transaction | -| [**CreateX**](https://github.com/pcaversaccio/createx) | `0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed` | Deterministic contract deployment | -| [**Permit2**](https://docs.uniswap.org/contracts/permit2/overview) | `0x000000000022d473030f116ddee9f6b43ac78ba3` | Token approvals and transfers | -| [**Arachnid Create2 Factory**](https://github.com/Arachnid/deterministic-deployment-proxy) | `0x4e59b44847b379578588920cA78FbF26c0B4956C` | CREATE2 deployment proxy | -| [**Safe Deployer**](https://github.com/safe-fndn/safe-singleton-factory) | `0x914d7Fec6aaC8cd542e72Bca78B30650d45643d7` | Safe deployer contract | +| [**Multicall3**](https://www.multicall3.com/) | `tempo1qr9pr00qt9mmxcc3vupgsc479gtnjak2zyetjw2l` | Batch multiple calls in one transaction | +| [**CreateX**](https://github.com/pcaversaccio/createx) | `tempo1qza9a5yevv7nkvf7f40hhhqnqhfu9za9a5kzeeav` | Deterministic contract deployment | +| [**Permit2**](https://docs.uniswap.org/contracts/permit2/overview) | `tempo1qqqqqqqqqq3dgucrpugkmhhf766r43ut5vkwg6pn` | Token approvals and transfers | +| [**Arachnid Create2 Factory**](https://github.com/Arachnid/deterministic-deployment-proxy) | `tempo1qp89ndzgg7ehj4u93zfqefu0hunvpdy4dsxdzh93` | CREATE2 deployment proxy | +| [**Safe Deployer**](https://github.com/safe-fndn/safe-singleton-factory) | `tempo1qzg56llvd2kge42zuu4u579nqegdg4jr6u5aqam2` | Safe deployer contract | ## Contract ABIs diff --git a/src/pages/quickstart/tokenlist.mdx b/src/pages/quickstart/tokenlist.mdx index 1ec420fb..631ed057 100644 --- a/src/pages/quickstart/tokenlist.mdx +++ b/src/pages/quickstart/tokenlist.mdx @@ -20,7 +20,7 @@ As an example, here's Tempo Testnet's tokenlist, fetched from [tokenlist.tempo.x [`/list/{chain_id}`](https://tokenlist.tempo.xyz/list/42431) | Token list for a chain | [`/asset/{chain_id}/{id}`](https://tokenlist.tempo.xyz/asset/42431/pathUSD) | Get a single token by symbol or address​ [`/icon/{chain_id}`](https://tokenlist.tempo.xyz/icon/42431) | Chain icon (SVG) | -[`/icon/{chain_id}/{address}`](https://tokenlist.tempo.xyz/icon/42431/0x20c0000000000000000000000000000000000000) | Token icon (SVG) | +[`/icon/{chain_id}/{address}`](https://tokenlist.tempo.xyz/icon/42431/tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqv0ywuh) | Token icon (SVG) | ## Adding a New Token diff --git a/src/pages/quickstart/wallet-developers.mdx b/src/pages/quickstart/wallet-developers.mdx index c2ddab9d..20091c0f 100644 --- a/src/pages/quickstart/wallet-developers.mdx +++ b/src/pages/quickstart/wallet-developers.mdx @@ -58,6 +58,28 @@ As a wallet developer, you can set the fee token for your user at the account le If you don't, Tempo uses a cascading fee token selection algorithm to determine the fee token for a transaction – learn more about [Fee Token Preferences](/protocol/fees/spec-fee#fee-token-preferences). ::: +### Display Tempo addresses + +Tempo uses a human-readable address format based on [bech32m encoding](/protocol/addresses) (BIP-350). All Tempo addresses start with the `tempo1` prefix and are exactly 46 characters long, providing instant chain recognition and built-in error detection for up to 4 character substitutions. + +Your wallet should display and accept `tempo1` addresses throughout the UI. Use the [bech32m library](/protocol/addresses#javascript--typescript-usage) to convert between the internal `0x` hex format used by the EVM and the user-facing `tempo1` format: + +```ts +import { encodeTempoAddress, decodeTempoAddress } from './lib/bech32m' + +// Display: convert internal hex to user-facing tempo1 +const display = encodeTempoAddress(account.address) +// → "tempo1qp6z6dwvvc6vq5efyk3ms39une6etu97hvwnw3n3" + +// Input: convert user-pasted tempo1 back to hex for transactions +const hex = decodeTempoAddress(userInput) +// → "0x742d35cc6634c0532925a3b844bc9e7595f0bebb" +``` + +:::tip +Display addresses with optional visual spacing for readability (e.g., `tempo1 qp6z6 dwvvc …`), but always copy the canonical form without spaces. See [Display Recommendations](/protocol/addresses#display-recommendations). +::: + ### Display token and network assets Tempo provides a public tokenlist service that hosts token and network assets. You can pull these assets from our public tokenlist service to display in your UI. @@ -116,7 +138,7 @@ Set the user's default fee token preference. This will be used for all transacti import { setUserToken } from 'viem/tempo' await client.fee.setUserTokenSync({ - token: '0x20c0000000000000000000000000000000000001', + token: 'tempo1qqsvqqqqqqqqqqqqqqqqqqqqqqqqqqqqqyr9xgnd', // AlphaUSD }) ``` @@ -130,12 +152,19 @@ Before launching Tempo support, ensure your wallet: - [ ] Hides or removes native balance display for Tempo - [ ] Displays `USD` as the currency symbol for gas - [ ] Quotes gas prices in the user's fee token +- [ ] Displays and accepts `tempo1` addresses ([format details](/protocol/addresses)) - [ ] Pulls token/network assets from Tempo's tokenlist - [ ] (Recommended) Integrates Tempo Transactions for enhanced UX ## Learning Resources +