Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/tempo/TokenAddress.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as Address from '../core/Address.js'
import * as TempoAddress from './TempoAddress.js'

export const prefix = '0x20c000000000000000000000'

/**
* Returns whether an address is a TIP-20 token address.
*
* [TIP-20 Token Standard](https://docs.tempo.xyz/protocol/tip20/overview)
*
* @example
* ```ts twoslash
* import { TokenAddress } from 'ox/tempo'
*
* const isTip20 = TokenAddress.isTip20('0x20c0000000000000000000000000000000000001')
* // @log: true
* ```
*
* @param address - Address to check.
* @returns Whether the address is a TIP-20 token address.
*/
export function isTip20(address: TempoAddress.Address): boolean {
const resolved = TempoAddress.resolve(address)
Address.assert(resolved, { strict: false })
return resolved.toLowerCase().startsWith(prefix)
}

export declare namespace isTip20 {
type ErrorType = Address.assert.ErrorType | TempoAddress.parse.ErrorType
}
20 changes: 19 additions & 1 deletion src/tempo/TokenId.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Hex } from 'ox'
import { TokenId } from 'ox/tempo'
import { TokenAddress, TokenId } from 'ox/tempo'
import { expect, test } from 'vitest'

test('from', () => {
Expand Down Expand Up @@ -29,6 +29,24 @@ test('fromAddress', () => {
expect(TokenId.fromAddress(tempoAddr)).toBe(1n)
})

test('fromAddress: invalid address', () => {
expect(() =>
TokenId.fromAddress('0x20c1000000000000000000000000000000000001'),
).toThrow('invalid tip20 address.')
})

test('TokenAddress.isTip20', () => {
expect(
TokenAddress.isTip20('0x20c0000000000000000000000000000000000001'),
).toBe(true)
expect(
TokenAddress.isTip20('tempox0x20c0000000000000000000000000000000000001'),
).toBe(true)
expect(
TokenAddress.isTip20('0x20c1000000000000000000000000000000000001'),
).toBe(false)
})

test('toAddress', () => {
expect(TokenId.toAddress(0n)).toBe(
'0x20c0000000000000000000000000000000000000',
Expand Down
11 changes: 6 additions & 5 deletions src/tempo/TokenId.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import * as Address from '../core/Address.js'
import * as Hash from '../core/Hash.js'
import * as Hex from '../core/Hex.js'
import * as TempoAddress from './TempoAddress.js'
import * as TokenAddress from './TokenAddress.js'

const tip20Prefix = '0x20c0'
const tip20Prefix = TokenAddress.prefix
const tip20PrefixSize = 12

export type TokenId = bigint
export type TokenIdOrAddress<addressType = Address.Address> =
Expand Down Expand Up @@ -55,9 +57,8 @@ export function from(tokenIdOrAddress: TokenIdOrAddress | number): TokenId {
*/
export function fromAddress(address: TempoAddress.Address): TokenId {
const resolved = TempoAddress.resolve(address)
if (!resolved.toLowerCase().startsWith(tip20Prefix))
throw new Error('invalid tip20 address.')
return Hex.toBigInt(Hex.slice(resolved, tip20Prefix.length))
if (!TokenAddress.isTip20(resolved)) throw new Error('invalid tip20 address.')
return Hex.toBigInt(Hex.slice(resolved, tip20PrefixSize))
}

/**
Expand All @@ -84,7 +85,7 @@ export function toAddress(
return resolved
}

const tokenIdHex = Hex.fromNumber(tokenId, { size: 18 })
const tokenIdHex = Hex.fromNumber(tokenId, { size: 8 })
return Hex.concat(tip20Prefix, tokenIdHex)
}

Expand Down
4 changes: 2 additions & 2 deletions src/tempo/VirtualMaster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import * as Hash from '../core/Hash.js'
import * as Hex from '../core/Hex.js'
import * as VirtualMasterPool from './internal/virtualMasterPool.js'
import * as TempoAddress from './TempoAddress.js'
import * as TokenAddress from './TokenAddress.js'
import * as VirtualAddress from './VirtualAddress.js'

const tip20Prefix = '0x20c000000000000000000000'
const zeroAddress = '0x0000000000000000000000000000000000000000'

/** A valid salt input for TIP-1022 master registration. */
Expand Down Expand Up @@ -680,7 +680,7 @@ function assertValidMasterAddress(address: Address.Address) {
'Virtual master address cannot itself be a virtual address.',
)

if (normalized.startsWith(tip20Prefix))
if (TokenAddress.isTip20(address))
throw new Errors.BaseError(
'Virtual master address cannot be a TIP-20 token address.',
)
Expand Down
6 changes: 6 additions & 0 deletions src/tempo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,12 @@ export * as TempoAddress from './TempoAddress.js'
* @category Reference
*/
export * as Tick from './Tick.js'
/**
* TIP-20 token address utilities.
*
* @category Reference
*/
export * as TokenAddress from './TokenAddress.js'
/**
* TIP-20 token ID utilities for converting between token IDs and addresses.
*
Expand Down
Loading