Skip to content

Commit

Permalink
feat(aens): support unicode names claim
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Jun 13, 2023
1 parent 481a654 commit f837e90
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 38 deletions.
38 changes: 19 additions & 19 deletions docs/guides/aens.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# AENS (æternity naming system)

## Introduction
This guide shows you how to perform all the operations that you need within the lifecycle of [æternity naming system (AENS)](https://docs.aeternity.com/protocol/AENS.html) using the SDK.
This guide shows you how to perform all the operations that you need within the lifecycle of [æternity naming system (AENS)](https://docs.aeternity.com/protocol/AENS/) using the SDK.

If you successfully claimed a name it will expire after 180000 keyblocks (~375 days). You will need to update your name before it expires!
If you successfully claimed a name it will expire after 180000 key blocks (~375 days). You will need to update your name before it expires!

## 1. Claim a name
Claiming an AENS name requires you (at least) 2 transactions:
Expand Down Expand Up @@ -36,7 +36,7 @@ console.log(preClaimTx)
tx: {
accountId: 'ak_2519mBsgjJEVEFoRgno1ryDsn3BEaCZGRbXPEjThWYLX9MTpmk',
commitmentId: 'cm_2igvW9egddKh77gDdE8mjotmL8PzE7Tf2Q639Q5stUqWQoEfap',
fee: 16620000000000,
fee: 16620000000000n,
nonce: 18,
type: 'NamePreclaimTx',
version: 1
Expand All @@ -52,20 +52,20 @@ console.log(preClaimTx)

Note:

- After transaction is included, you have `300` keyblocks to broadcast `claim` transaction with
- After transaction is included, you have `300` key blocks to broadcast `claim` transaction with
the same `salt` and it should be signed with the same private key as `pre-claim`.
- As the `pre-claim` is required to avoid front running it is recommended to wait with the actual `claim` until at least 1 keyblock has been mined so that nobody knows which name you aim to claim.
- The corresponding `claim` cannot be included in the same keyblock anyway. The protocol doesn't allow that.
- As the `pre-claim` is required to avoid front running it is recommended to wait with the actual `claim` until at least 1 key block has been mined so that nobody knows which name you aim to claim.
- The corresponding `claim` cannot be included in the same key block anyway. The protocol doesn't allow that.
- You should check if the name is still available before performing a `pre-claim`. The protocol itself doesn't reject a `pre-claim` transaction if the name isn't available anymore.
- As you can see above in the logs the result (`preClaimTx`) of the `aensPreclaim` has bound a `claim` function that you can make use of to perform the actual claim.
- In case you want to perform the actual claim at a later point you should remember the `salt` that has been used for the `pre-claim`

### Claim
This example assumes that you perform the actual `claim` manually by having remembered the required `salt`. In most cases you'd probably use the `claim` function that is bound to the result of the `aensPreclaim` function directly (see above).

Depending on the length of the name the actual `claim` will result in direct ownership of the AENS name or start an auction:
Depending on the length of the name in punycode the actual `claim` will result in direct ownership of the AENS name or start an auction:

- `Name length` > 12: ownership of the name is immediately transfered to your account
- `Name length` > 12: ownership of the name is immediately transferred to your account
- `Name length` <= 12: an `auction` is started

```js
Expand All @@ -87,10 +87,10 @@ console.log(claimTx)
],
tx: {
accountId: 'ak_2519mBsgjJEVEFoRgno1ryDsn3BEaCZGRbXPEjThWYLX9MTpmk',
fee: 16800000000000,
fee: 16800000000000n,
name: 'testNameForTheGuide.chain',
nameFee: '2865700000000000000',
nameSalt: '7595805618692717',
nameFee: 2865700000000000000n,
nameSalt: 7595805618692717,
nonce: 19,
type: 'NameClaimTx',
version: 2
Expand Down Expand Up @@ -131,7 +131,7 @@ const increment = 0.05 // 5%, the minimum required increment
// startFee is OPTIONAL and defaults to minimum calculated fee for the name in general
// startFee MUST be at least the nameFee of the last bid
// increment is OPTIONAL and defaults to 0.05
const nameFee = computeBidFee(name, startFee, increment)
const nameFee = computeBidFee(name, { startFee, increment })
const bidTx = await aeSdk.aensBid(name, nameFee)

console.log(bidTx)
Expand All @@ -147,9 +147,9 @@ console.log(`BID PLACED AT ${bidTx.blockHeight} WILL END AT ${computeAuctionEndB
],
tx: {
accountId: 'ak_2519mBsgjJEVEFoRgno1ryDsn3BEaCZGRbXPEjThWYLX9MTpmk',
fee: 16520000000000,
fee: 16520000000000n,
name: 'auctiontest1.chain',
nameFee: '3159434250000000000',
nameFee: 3159434250000000000n,
nameSalt: 0,
nonce: 24,
type: 'NameClaimTx',
Expand All @@ -172,7 +172,7 @@ Now that you own your AENS name you might want to update it in order to:

- Set pointers to `accounts`, `oracles`, `contracts` or `channels`.
- Extend the TTL before it expires.
- By default a name will have a TTL of 180000 keyblocks (~375 days). It cannot be extended longer than 180000 keyblocks.
- By default a name will have a TTL of 180000 key blocks (~375 days). It cannot be extended longer than 180000 key blocks.

### Set pointers & update TTL
```js
Expand Down Expand Up @@ -208,7 +208,7 @@ console.log(nameUpdateTx)
tx: {
accountId: 'ak_2519mBsgjJEVEFoRgno1ryDsn3BEaCZGRbXPEjThWYLX9MTpmk',
clientTtl: 84600,
fee: 17800000000000,
fee: 17800000000000n,
nameId: 'nm_1Cz5HGY8PMWZxNrM6s51CtsJZDU3DDT1LdmpEipa3DRghyGz5',
nameTtl: 180000,
nonce: 27,
Expand Down Expand Up @@ -264,7 +264,7 @@ console.log(nameUpdateTx)
tx: {
accountId: 'ak_2519mBsgjJEVEFoRgno1ryDsn3BEaCZGRbXPEjThWYLX9MTpmk',
clientTtl: 100000,
fee: 17800000000000,
fee: 17800000000000n,
nameId: 'nm_1Cz5HGY8PMWZxNrM6s51CtsJZDU3DDT1LdmpEipa3DRghyGz5',
nameTtl: 100000,
nonce: 28,
Expand Down Expand Up @@ -308,7 +308,7 @@ console.log(nameTransferTx)
],
tx: {
accountId: 'ak_2519mBsgjJEVEFoRgno1ryDsn3BEaCZGRbXPEjThWYLX9MTpmk',
fee: 17300000000000,
fee: 17300000000000n,
nameId: 'nm_1Cz5HGY8PMWZxNrM6s51CtsJZDU3DDT1LdmpEipa3DRghyGz5',
nonce: 33,
recipientId: 'ak_21A27UVVt3hDkBE5J7rhhqnH5YNb4Y1dqo4PnSybrH85pnWo7E',
Expand Down Expand Up @@ -356,7 +356,7 @@ console.log(nameRevokeTx)
Note:

- On revocation the name enters in a `revoked` state.
- After a timeout of `2016` keyblocks the name will be available for claiming again.
- After a timeout of `2016` key blocks the name will be available for claiming again.

## Delegate signature to contract (AENS interface)
It is possible to authorize a Sophia contract to manage an AENS name on behalf of your account. In order to achieve that you need to provide a delegation signature to the contract. The contract will then be able to use the [AENS interface](https://docs.aeternity.com/aesophia/latest/sophia_features/#aens-interface) and perform AENS related actions on behalf of your account.
Expand Down
2 changes: 1 addition & 1 deletion src/aens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ interface AensPreclaimOptions extends
* @example
* ```js
* const name = 'test.chain'
* const bidFee = computeBidFee(name, startFee, incrementPercentage)
* const bidFee = computeBidFee(name, { startFee, increment: 0.42 })
*
* await sdkInstance.aensBid(name, 213109412839123, { ttl, fee, nonce })
* ```
Expand Down
2 changes: 1 addition & 1 deletion src/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { buildTxHash } from './tx/builder';
* @category chain
*/
export function _getPollInterval(
type: 'block' | 'microblock',
type: 'block' | 'microblock', // TODO: rename to 'key-block' | 'micro-block'
{ _expectedMineRate = 180000, _microBlockCycle = 3000, _maxPollInterval = 5000 }:
{ _expectedMineRate?: number; _microBlockCycle?: number; _maxPollInterval?: number },
): number {
Expand Down
10 changes: 5 additions & 5 deletions src/tx/builder/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function nameToPunycode(maybeName: string): AensName {
* @returns `nm_` prefixed encoded AENS name
*/
export function produceNameId(name: AensName): Encoded.Name {
return encode(hash(name.toLowerCase()), Encoding.Name);
return encode(hash(nameToPunycode(name)), Encoding.Name);
}

/**
Expand All @@ -116,7 +116,7 @@ export function commitmentHash(
): Encoded.Commitment {
return encode(
hash(concatBuffers([
Buffer.from(name.toLowerCase()),
Buffer.from(nameToPunycode(name)),
Buffer.from(salt.toString(16).padStart(64, '0'), 'hex'),
])),
Encoding.Commitment,
Expand Down Expand Up @@ -190,7 +190,7 @@ export function getDefaultPointerKey(
* @returns the minimum fee for the AENS name auction
*/
export function getMinimumNameFee(name: AensName): BigNumber {
const nameLength = name.length - AENS_SUFFIX.length;
const nameLength = nameToPunycode(name).length - AENS_SUFFIX.length;
return NAME_BID_RANGES[Math.min(nameLength, NAME_MAX_LENGTH_FEE)];
}

Expand Down Expand Up @@ -226,7 +226,7 @@ export function computeBidFee(
* @returns Auction end height
*/
export function computeAuctionEndBlock(name: AensName, claimHeight: number): number {
const length = name.length - AENS_SUFFIX.length;
const length = nameToPunycode(name).length - AENS_SUFFIX.length;
const h = (length <= 4 ? 62 * NAME_BID_TIMEOUT_BLOCKS : null)
?? (length <= 8 ? 31 * NAME_BID_TIMEOUT_BLOCKS : null)
?? (length <= 12 ? NAME_BID_TIMEOUT_BLOCKS : null)
Expand All @@ -239,5 +239,5 @@ export function computeAuctionEndBlock(name: AensName, claimHeight: number): num
* @category AENS
*/
export function isAuctionName(name: AensName): boolean {
return name.length < 13 + AENS_SUFFIX.length;
return nameToPunycode(name).length < 13 + AENS_SUFFIX.length;
}
4 changes: 2 additions & 2 deletions test/integration/Middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ describe('MiddlewareSubscriber', () => {
mdwGensPerMinute: res.mdwGensPerMinute,
mdwHeight: res.mdwHeight,
mdwLastMigration: 20230519120000,
mdwRevision: '2d3ae3d',
mdwRevision: res.mdwRevision,
mdwSynced: true,
mdwSyncing: true,
mdwTxIndex: res.mdwTxIndex,
mdwVersion: '1.49.0',
mdwVersion: res.mdwVersion,
nodeHeight: res.nodeHeight,
nodeProgress: 100,
nodeRevision: 'a42c1b1e84dabdad350005213a2a9334113a6832',
Expand Down
3 changes: 2 additions & 1 deletion test/integration/MiddlewareSubscriber.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ describe('MiddlewareSubscriber', () => {
return response.json();
}

it('subscribes for new transactions', async () => {
// TODO: enable after solving https://github.com/aeternity/ae_mdw/issues/1336
it.skip('subscribes for new transactions', async () => {
const [{ hash }, transaction] = await Promise.all([
aeSdk.spend(1, account.address),
new Promise((resolve, reject) => {
Expand Down
70 changes: 63 additions & 7 deletions test/integration/aens.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { describe, it, before } from 'mocha';
import { expect } from 'chai';
import { getSdk } from '.';
import { assertNotNull, randomName } from '../utils';
import { assertNotNull, randomName, randomString } from '../utils';
import {
AeSdk, generateKeyPair, buildContractId, computeAuctionEndBlock, computeBidFee,
AeSdk, generateKeyPair, buildContractId, computeBidFee, ensureName, produceNameId,
AensPointerContextError, UnexpectedTsError, encode, decode, Encoding, ContractMethodsBase,
} from '../../src';
import { pause } from '../../src/utils/other';
Expand All @@ -16,7 +16,7 @@ describe('Aens', () => {
aeSdk = await getSdk(2);
});

it('claims names', async () => {
it('claims a name', async () => {
const preclaim = await aeSdk.aensPreclaim(name);
expect(preclaim.commitmentId).to.satisfy((s: string) => s.startsWith('cm_'));

Expand All @@ -25,6 +25,54 @@ describe('Aens', () => {
expect(claimed.ttl).to.be.an('number');
});

it('claims a unicode name', async () => {
const n = `испытаниЕ-æpP-${randomString(4)}.chain`;
ensureName(n);
const preclaim = await aeSdk.aensPreclaim(n);
const claimed = await preclaim.claim();

expect(claimed.ttl).to.be.a('number');
expect(claimed.update).to.be.a('function');
expect(claimed.transfer).to.be.a('function');
expect(claimed.revoke).to.be.a('function');
expect(claimed.extendTtl).to.be.a('function');
assertNotNull(claimed.tx);
assertNotNull(claimed.signatures);
expect(claimed).to.be.eql({
tx: {
fee: 16960000000000n,
nonce: claimed.tx.nonce,
accountId: aeSdk.address,
name: n,
nameSalt: claimed.tx.nameSalt,
nameFee: 300000000000000n,
version: 2,
type: 'NameClaimTx',
},
blockHeight: claimed.blockHeight,
blockHash: claimed.blockHash,
hash: claimed.hash,
signatures: [claimed.signatures[0]],
rawTx: claimed.rawTx,
id: produceNameId(n),
owner: aeSdk.address,
ttl: claimed.ttl,
pointers: [],
update: claimed.update,
transfer: claimed.transfer,
revoke: claimed.revoke,
extendTtl: claimed.extendTtl,
});

const queried = await aeSdk.api.getNameEntryByName(n);
expect(queried).to.eql({
id: produceNameId(n),
owner: aeSdk.address,
ttl: claimed.ttl,
pointers: [],
});
});

it('queries names', async () => {
// For some reason the node will return 404 when name is queried
// just right after claim tx has been mined so we wait 0.5s
Expand Down Expand Up @@ -152,7 +200,7 @@ describe('Aens', () => {
});

describe('name auctions', () => {
it('claims names', async () => {
it('claims a name', async () => {
const onAccount = aeSdk.addresses().find((acc) => acc !== aeSdk.address);
const nameShort = randomName(12);

Expand All @@ -168,10 +216,18 @@ describe('Aens', () => {
bid.should.be.an('object');

await expect(aeSdk.getName(nameShort)).to.be.rejectedWith('error: Name not found');
});

it('claims a unicode name', async () => {
const onAccount = aeSdk.addresses().find((acc) => acc !== aeSdk.address);
const nameShort = ${randomString(4)}.chain`;
ensureName(nameShort);

if (bid.blockHeight == null) throw new UnexpectedTsError();
const auctionEndBlock = computeAuctionEndBlock(nameShort, bid.blockHeight);
console.log(`BID STARTED AT ${bid.blockHeight} WILL END AT ${auctionEndBlock}`);
const preclaim = await aeSdk.aensPreclaim(nameShort);
await preclaim.claim();
await aeSdk.aensBid(nameShort, computeBidFee(nameShort), { onAccount });

await expect(aeSdk.getName(nameShort)).to.be.rejectedWith('error: Name not found');
});
});
});
40 changes: 39 additions & 1 deletion test/unit/aens.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, it } from 'mocha';
import { expect } from 'chai';
import { ensureName, ArgumentError } from '../../src';
import {
ensureName, ArgumentError, isAuctionName, computeAuctionEndBlock,
} from '../../src';
import { nameToPunycode } from '../../src/tx/builder/helpers';

const tests = [{
Expand Down Expand Up @@ -294,4 +296,40 @@ describe('AENS utils', () => {
.to.throw(ArgumentError, 'aens name should be not too long, got ldiDxa1Yxy1iiTRztYEN4F8nrnfZib3Q1MllPghmst8fjJ1sI3DXzOoAddE2ETxp.chain instead');
});
});

describe('isAuctionName', () => {
it('checks non-auction name', () => {
expect(isAuctionName('1234567890123.chain')).to.be.equal(false);
});

it('checks auction name', () => {
expect(isAuctionName('123456789012.chain')).to.be.equal(true);
});

it('checks non-auction unicode name', () => {
expect(isAuctionName('æ23456.chain')).to.be.equal(false);
});

it('checks auction unicode name', () => {
expect(isAuctionName('æ2345.chain')).to.be.equal(true);
});
});

describe('computeAuctionEndBlock', () => {
it('computes for longest auction', () => {
expect(computeAuctionEndBlock('123456789012.chain', 1)).to.be.equal(481);
});

it('computes for shortest auction', () => {
expect(computeAuctionEndBlock('1.chain', 1)).to.be.equal(29761);
});

it('computes for longest unicode auction', () => {
expect(computeAuctionEndBlock('æ2345.chain', 1)).to.be.equal(481);
});

it('computes for shortest unicode auction', () => {
expect(computeAuctionEndBlock('æ.chain', 1)).to.be.equal(14881);
});
});
});
2 changes: 1 addition & 1 deletion test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import BigNumber from 'bignumber.js';
import { expect } from 'chai';
import { AensName } from '../src';

function randomString(len: number): string {
export function randomString(len: number): string {
const charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
let string = '';
for (let i = 0; i < len; i += 1) {
Expand Down

0 comments on commit f837e90

Please sign in to comment.