Skip to content

Commit

Permalink
feat(io transfer): add transfer api to ario writable client
Browse files Browse the repository at this point in the history
  • Loading branch information
Atticus committed Apr 19, 2024
1 parent 1a1c971 commit 0d37623
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 15 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@ const decreaseOperatorStakeTx =
// fDrr0_J4Iurt7caNST02cMotaz2FIbWQ4Kcj616RHl3
```

### `saveObservations({ reportTxId, failedGateways })`
#### `saveObservations({ reportTxId, failedGateways })`

Saves the observations of the current epoch. Requires `signer` to be provided on `ArIO.init` to sign the transaction.

Expand All @@ -737,6 +737,19 @@ const saveObservationsTx = await authenticatedArIO.saveObservations(params);
// fDrr0_J4Iurt7caNST02cMotaz2FIbWQ4Kcj616RHl3
```

#### `transfer({ target, qty, denomination })`

Transfers `IO` or `mIO` depending on the `denomination` selected, defaulting as `IO`, to the designated `target` recipient address. Requires `signer` to be provided on `ArIO.init` to sign the transaction.

```typescript
// signer required for write interactions APIs
const authenticatedArIO = ArIO.init({ signer });
const decreaseOperatorStakeTx = await authenticatedArIO.transfer({
target: '-5dV7nk7waR8v4STuwPnTck1zFVkQqJh5K9q9Zik4Y5',
qty: 1000,
});
```

### Custom Contracts

The ArIO contract client class exposes APIs relevant to the ar.io contract. It can be configured to use any contract ID that adheres to the spec of the ar.io contract. In the default case, it will automatically build and utilize a contract data provider interface that is configured to point the the known mainnet contract ID at construction time. You can provide custom contract data provider or, alternatively, a `contractTxId` to the ArIO constructor to use a different, ar.io-spec-compatible contract.
Expand Down
10 changes: 10 additions & 0 deletions src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ArIOState,
ArNSAuctionData,
ArNSNameData,
DENOMINATIONS,
EpochDistributionData,
Gateway,
GatewayConnectionSettings,
Expand Down Expand Up @@ -170,6 +171,15 @@ export interface ArIOReadContract extends BaseContract<ArIOState> {

export interface ArIOWriteContract {
// write interactions
transfer({
target,
qty,
denomination,
}: {
target: WalletAddress;
qty: number;
denomination: DENOMINATIONS;
}): Promise<WriteInteractionResult>;
joinNetwork({
qty,
allowDelegatedStaking,
Expand Down
21 changes: 21 additions & 0 deletions src/common/ar-io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
ArNSNameData,
ContractConfiguration,
ContractSigner,
DENOMINATIONS,
EpochDistributionData,
EvaluationOptions,
EvaluationParameters,
Expand Down Expand Up @@ -315,6 +316,26 @@ export class ArIOWritable extends ArIOReadable implements ArIOWriteContract {
this.signer = signer;
}

async transfer({
target,
qty,
denomination = DENOMINATIONS.IO,
}: {
target: string;
qty: number;
denomination: DENOMINATIONS;
}): Promise<WriteInteractionResult> {
return this.contract.writeInteraction({
functionName: AR_IO_CONTRACT_FUNCTIONS.TRANSFER,
inputs: {
target,
qty,
denomination,
},
signer: this.signer,
});
}

async joinNetwork(
params: JoinNetworkParams,
): Promise<WriteInteractionResult> {
Expand Down
5 changes: 5 additions & 0 deletions src/contract-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ export type VaultData = {

// Balances

export enum DENOMINATIONS {
IO = 'IO',
MIO = 'mIO',
}

export type Balances = Record<WalletAddress, number>;

export type Fees = Record<string, number>;
Expand Down
1 change: 1 addition & 0 deletions tests/integration/ar-io-writable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const writeTestCases = [
['decreaseDelegateStake', { target: gatewayAddress, qty: 101 }],
['increaseOperatorStake', { qty: 101 }],
['decreaseOperatorStake', { qty: 101 }],
['transfer', { target: ''.padEnd(43, 'f'), qty: 101 }],
] as const;

describe('ArIO Client', () => {
Expand Down
88 changes: 74 additions & 14 deletions tests/integration/arlocal/ar-io-contract/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ var mIOToken = class extends PositiveFiniteInteger {
var TOTAL_IO_SUPPLY = new IOToken(1e9).toMIO();
var SECONDS_IN_A_YEAR = 31536e3;
var BLOCKS_PER_DAY = 720;
var GATEWAY_LEAVE_BLOCK_LENGTH = new BlockHeight(90 * BLOCKS_PER_DAY);
var GATEWAY_LEAVE_BLOCK_LENGTH = new BlockHeight(21 * BLOCKS_PER_DAY);
var GATEWAY_REDUCE_STAKE_BLOCK_LENGTH = 30 * BLOCKS_PER_DAY;
var MAX_TOKEN_LOCK_BLOCK_LENGTH = 12 * 365 * BLOCKS_PER_DAY;
var MIN_TOKEN_LOCK_BLOCK_LENGTH = 14 * BLOCKS_PER_DAY;
Expand Down Expand Up @@ -2866,6 +2866,18 @@ function validate14(
return errors === 0;
}
var validateTransferToken = validate15;
var schema16 = {
$id: '#/definitions/transferTokens',
type: 'object',
properties: {
function: { type: 'string', const: 'transfer' },
target: { type: 'string', pattern: '^[a-zA-Z0-9-_]{43}$' },
qty: { type: 'number', minimum: 1 },
denomination: { type: 'string', enum: ['IO', 'mIO'], default: 'mIO' },
},
required: ['target', 'qty'],
additionalProperties: false,
};
function validate15(
data,
{ instancePath = '', parentData, parentDataProperty, rootData = data } = {},
Expand Down Expand Up @@ -2904,7 +2916,14 @@ function validate15(
errors++;
}
for (const key0 in data) {
if (!(key0 === 'function' || key0 === 'target' || key0 === 'qty')) {
if (
!(
key0 === 'function' ||
key0 === 'target' ||
key0 === 'qty' ||
key0 === 'denomination'
)
) {
const err2 = {
instancePath,
schemaPath: '#/additionalProperties',
Expand Down Expand Up @@ -3021,18 +3040,51 @@ function validate15(
errors++;
}
}
if (data.denomination !== void 0) {
let data3 = data.denomination;
if (typeof data3 !== 'string') {
const err9 = {
instancePath: instancePath + '/denomination',
schemaPath: '#/properties/denomination/type',
keyword: 'type',
params: { type: 'string' },
message: 'must be string',
};
if (vErrors === null) {
vErrors = [err9];
} else {
vErrors.push(err9);
}
errors++;
}
if (!(data3 === 'IO' || data3 === 'mIO')) {
const err10 = {
instancePath: instancePath + '/denomination',
schemaPath: '#/properties/denomination/enum',
keyword: 'enum',
params: { allowedValues: schema16.properties.denomination.enum },
message: 'must be equal to one of the allowed values',
};
if (vErrors === null) {
vErrors = [err10];
} else {
vErrors.push(err10);
}
errors++;
}
}
} else {
const err9 = {
const err11 = {
instancePath,
schemaPath: '#/type',
keyword: 'type',
params: { type: 'object' },
message: 'must be object',
};
if (vErrors === null) {
vErrors = [err9];
vErrors = [err11];
} else {
vErrors.push(err9);
vErrors.push(err11);
}
errors++;
}
Expand Down Expand Up @@ -5643,7 +5695,11 @@ function getPriceForInteraction(state, { caller, input }) {
currentBlockTimestamp: interactionTimestamp,
years,
});
fee = calculateAnnualRenewalFee({ name, years, fees: state.fees });
fee = calculateAnnualRenewalFee({
name,
years,
fees: state.fees,
}).multiply(state.demandFactoring.demandFactor);
break;
}
case 'increaseUndernameCount': {
Expand Down Expand Up @@ -6023,12 +6079,15 @@ var evolveState = async (state, { caller }) => {
if (caller !== owner) {
throw new ContractError(NON_CONTRACT_OWNER_MESSAGE);
}
const updatedFees = Object.keys(state.fees).reduce((acc, key) => {
const existingFee = new IOToken(state.fees[key]);
acc[key] = existingFee.toMIO().valueOf();
return acc;
}, {});
state.fees = updatedFees;
for (const [address, gateway] of Object.entries(state.gateways)) {
if (gateway.status === 'leaving') {
const currentEndFor90Days = gateway.end;
const correctEndFor21Days =
currentEndFor90Days - 69 * BLOCKS_PER_DAY + 4 * BLOCKS_PER_DAY;
state.gateways[address].end = correctEndFor21Days;
state.gateways[address].vaults[address].end = correctEndFor21Days;
}
}
return { state };
};

Expand Down Expand Up @@ -7160,9 +7219,10 @@ var TransferToken = class {
getInvalidAjvMessage(validateTransferToken, input, 'transferToken'),
);
}
const { target, qty } = input;
const { target, qty, denomination = 'mIO' } = input;
this.target = target;
this.qty = new IOToken(qty).toMIO();
this.qty =
denomination === 'mIO' ? new mIOToken(qty) : new IOToken(qty).toMIO();
}
};
var transferTokens = async (state, { caller, input }) => {
Expand Down

0 comments on commit 0d37623

Please sign in to comment.