Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(AENS): Add ability to spend by name #682

Merged
merged 11 commits into from
Sep 26, 2019
17 changes: 9 additions & 8 deletions es/ae/aens.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@

import * as R from 'ramda'
import { encodeBase58Check, salt } from '../utils/crypto'
import { commitmentHash } from '../tx/builder/helpers'
import { commitmentHash, isNameValid } from '../tx/builder/helpers'
import Ae from './'
import { CLIENT_TTL, NAME_TTL } from '../tx/builder/schema'

/**
* Transfer a domain to another account
Expand Down Expand Up @@ -132,6 +133,7 @@ async function update (nameId, target, options = {}) {
* @return {Promise<Object>}
*/
async function query (name, opt = {}) {
isNameValid(name)
const o = await this.getName(name)
const nameId = o.id

Expand Down Expand Up @@ -165,6 +167,7 @@ async function query (name, opt = {}) {
* @return {Promise<Object>} the result of the claim
*/
async function claim (name, salt, options = {}) {
isNameValid(name)
const opt = R.merge(this.Ae.defaults, options)
const claimTx = await this.nameClaimTx(R.merge(opt, {
accountId: await this.address(opt),
Expand All @@ -173,11 +176,8 @@ async function claim (name, salt, options = {}) {
}))

const result = await this.send(claimTx, opt)

return {
...result,
...opt.waitMined && await this.aensQuery(name, opt)
}
const nameInter = this.Chain.defaults.waitMined ? await this.aensQuery(name, opt) : {}
return Object.assign(result, nameInter)
}

/**
Expand All @@ -190,6 +190,7 @@ async function claim (name, salt, options = {}) {
* @return {Promise<Object>}
*/
async function preclaim (name, options = {}) {
isNameValid(name)
const opt = R.merge(this.Ae.defaults, options)
const _salt = salt()
const height = await this.height()
Expand Down Expand Up @@ -234,8 +235,8 @@ const Aens = Ae.compose({
deepProps: {
Ae: {
defaults: {
clientTtl: 1,
nameTtl: 50000 // aec_governance:name_claim_max_expiration() => 50000
clientTtl: CLIENT_TTL,
nameTtl: NAME_TTL // aec_governance:name_claim_max_expiration() => 50000
}
}
}
Expand Down
28 changes: 25 additions & 3 deletions es/ae/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import Account from '../account'
import TxBuilder from '../tx/builder'
import * as R from 'ramda'
import { BigNumber } from 'bignumber.js'
import { isAddressValid } from '../utils/crypto'
import { isNameValid } from '../tx/builder/helpers'

/**
* Sign and post a transaction to the chain
Expand Down Expand Up @@ -63,16 +65,36 @@ async function signUsingGA (tx, options = {}) {
* @category async
* @rtype (amount: Number|String, recipientId: String, options?: Object) => Promise[String]
* @param {Number|String} amount - Amount to spend
* @param {String} recipientId - Address of recipient account
* @param {String} recipientId - Address or Name of recipient account
* @param {Object} options - Options
* @return {String|String} Transaction or transaction hash
*/
async function spend (amount, recipientId, options = {}) {
const opt = R.merge(this.Ae.defaults, options)
const spendTx = await this.spendTx(R.merge(opt, { senderId: await this.address(opt), recipientId, amount: amount }))
recipientId = await this.resolveRecipientName(recipientId)
const spendTx = await this.spendTx(R.merge(opt, { senderId: await this.address(opt), recipientId, amount }))
return this.send(spendTx, opt)
}

/**
* Resolve AENS name and return name hash
*
* @param {String} nameOrAddress
* @param {String} pointerPrefix
* @return {String} Address or AENS name hash
*/
async function resolveRecipientName (nameOrAddress) {
if (isAddressValid(nameOrAddress)) return nameOrAddress
if (isNameValid(nameOrAddress)) {
const { id } = await this.getName(nameOrAddress)
return id
}
// Validation
// const { id: nameHash, pointers } = await this.getName(nameOrAddress)
// if (pointers.find(({ id }) => id.split('_')[0] === pointerPrefix)) return nameHash
// throw new Error(`Can't find pointers with prefix ${pointerPrefix} for name ${nameOrAddress}`)
}

/**
* Send a percentage of funds to another account
* @instance
Expand Down Expand Up @@ -135,7 +157,7 @@ function destroyInstance () {
* @return {Object} Ae instance
*/
const Ae = stampit(Tx, Account, Chain, {
methods: { send, spend, transferFunds, destroyInstance },
methods: { send, spend, transferFunds, destroyInstance, resolveRecipientName },
deepProps: { Ae: { defaults: {} } }
// Todo Enable GA
// deepConfiguration: { Ae: { methods: ['signUsingGA'] } }
Expand Down
20 changes: 18 additions & 2 deletions es/tx/builder/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as R from 'ramda'
import {
assertedType,
decodeBase58Check,
Expand All @@ -8,7 +9,7 @@ import {
salt
} from '../../utils/crypto'
import { toBytes } from '../../utils/bytes'
import { ID_TAG_PREFIX, PREFIX_ID_TAG } from './schema'
import { ID_TAG_PREFIX, PREFIX_ID_TAG, AENS_NAME_DOMAINS } from './schema'
import { BigNumber } from 'bignumber.js'

/**
Expand Down Expand Up @@ -205,6 +206,20 @@ export function readPointers (pointers) {
)
}

/**
* Is name valid
* @function
* @alias module:@aeternity/aepp-sdk/es/ae/aens
* @param {string} name
* @return Boolean
* @throws Error
*/
export function isNameValid (name) {
if (typeof name !== 'string') throw new Error('AENS: Name must be a string')
if (!AENS_NAME_DOMAINS.includes(R.last(name.split('.')))) throw new Error(`AENS: Invalid name domain. Possible domains [${AENS_NAME_DOMAINS}]`)
return true
}

export default {
readPointers,
buildPointers,
Expand All @@ -219,5 +234,6 @@ export default {
formatSalt,
oracleQueryId,
createSalt,
buildHash
buildHash,
isNameValid
}
4 changes: 4 additions & 0 deletions es/tx/builder/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ function validateField (value, key, type, prefix) {
return assert((!isNaN(value) || BigNumber.isBigNumber(value)) && BigNumber(value).gte(0), { value, isMinusValue })
}
case FIELD_TYPES.id:
if (Array.isArray(prefix)) {
const p = prefix.find(p => p === value.split('_')[0])
return assert(p && PREFIX_ID_TAG[value.split('_')[0]], { value, prefix })
}
return assert(assertedType(value, prefix) && PREFIX_ID_TAG[value.split('_')[0]] && value.split('_')[0] === prefix, { value, prefix })
case FIELD_TYPES.binary:
return assert(value.split('_')[0] === prefix, { prefix, value })
Expand Down
18 changes: 12 additions & 6 deletions es/tx/builder/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
import BigNumber from 'bignumber.js'

export const VSN = 1
export const VSN_2 = 2

// # AENS
export const AENS_NAME_DOMAINS = ['aet', 'test']
export const CLIENT_TTL = 1
export const NAME_TTL = 50000

// # Tag constant for ids (type uint8)
// # see https://github.com/aeternity/protocol/blob/master/serializations.md#the-id-type
Expand Down Expand Up @@ -374,14 +380,14 @@ const ACCOUNT_TX_2 = [
TX_FIELD('flags', FIELD_TYPES.int),
TX_FIELD('nonce', FIELD_TYPES.int),
TX_FIELD('balance', FIELD_TYPES.int),
TX_FIELD('gaContract', FIELD_TYPES.id, 'ct'),
TX_FIELD('gaContract', FIELD_TYPES.id, ['ct', 'nm']),
TX_FIELD('gaAuthFun', FIELD_TYPES.binary, 'cb')
]

const SPEND_TX = [
...BASE_TX,
TX_FIELD('senderId', FIELD_TYPES.id, 'ak'),
TX_FIELD('recipientId', FIELD_TYPES.id, 'ak'),
TX_FIELD('recipientId', FIELD_TYPES.id, ['ak', 'nm']),
TX_FIELD('amount', FIELD_TYPES.int),
TX_FIELD('fee', FIELD_TYPES.int),
TX_FIELD('ttl', FIELD_TYPES.int),
Expand Down Expand Up @@ -431,7 +437,7 @@ const NAME_TRANSFER_TX = [
TX_FIELD('accountId', FIELD_TYPES.id, 'ak'),
TX_FIELD('nonce', FIELD_TYPES.int),
TX_FIELD('nameId', FIELD_TYPES.id, 'nm'),
TX_FIELD('recipientId', FIELD_TYPES.id, 'ak'),
TX_FIELD('recipientId', FIELD_TYPES.id, ['ak', 'nm']),
TX_FIELD('fee', FIELD_TYPES.int),
TX_FIELD('ttl', FIELD_TYPES.int)
]
Expand Down Expand Up @@ -501,7 +507,7 @@ const CONTRACT_CALL_TX = [
...BASE_TX,
TX_FIELD('callerId', FIELD_TYPES.id, 'ak'),
TX_FIELD('nonce', FIELD_TYPES.int),
TX_FIELD('contractId', FIELD_TYPES.id, 'ct'),
TX_FIELD('contractId', FIELD_TYPES.id, ['ct', 'nm']),
TX_FIELD('abiVersion', FIELD_TYPES.int),
TX_FIELD('fee', FIELD_TYPES.int),
TX_FIELD('ttl', FIELD_TYPES.int),
Expand Down Expand Up @@ -541,7 +547,7 @@ const ORACLE_REGISTER_TX = [

const ORACLE_EXTEND_TX = [
...BASE_TX,
TX_FIELD('oracleId', FIELD_TYPES.id, 'ok'),
TX_FIELD('oracleId', FIELD_TYPES.id, ['ok', 'nm']),
TX_FIELD('nonce', FIELD_TYPES.int),
TX_FIELD('oracleTtlType', FIELD_TYPES.int),
TX_FIELD('oracleTtlValue', FIELD_TYPES.int),
Expand All @@ -553,7 +559,7 @@ const ORACLE_QUERY_TX = [
...BASE_TX,
TX_FIELD('senderId', FIELD_TYPES.id, 'ak'),
TX_FIELD('nonce', FIELD_TYPES.int),
TX_FIELD('oracleId', FIELD_TYPES.id, 'ok'),
TX_FIELD('oracleId', FIELD_TYPES.id, ['ok', 'nm']),
TX_FIELD('query', FIELD_TYPES.string),
TX_FIELD('queryFee', FIELD_TYPES.int),
TX_FIELD('queryTtlType', FIELD_TYPES.int),
Expand Down
8 changes: 8 additions & 0 deletions test/integration/aens.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ describe('Aens', function () {
configure(this)

let aens
let nameHash
const account = generateKeyPair()
const name = randomName()

Expand Down Expand Up @@ -68,12 +69,19 @@ describe('Aens', function () {

it('updates names', async () => {
const claim = await aens.aensQuery(name)
nameHash = claim.id
const address = await aens.address()
return claim.update(address).should.eventually.deep.include({
pointers: [R.fromPairs([['key', 'account_pubkey'], ['id', address]])]
})
})

it('Spend by name', async () => {
const current = await aens.address()
const onAccount = aens.addresses().find(acc => acc !== current)
await aens.spend(100, name, { onAccount })
})

it('transfers names', async () => {
const claim = await aens.aensQuery(name)

Expand Down